Compare commits

...

43 Commits

Author SHA1 Message Date
61b9435d1f bump: version 1.0.0-a3 → 1.0.0-a4 2024-07-18 12:59:03 +00:00
Jon
8244676530 test: ensure inventory upload matches by both serial number and uuid if device name different
!42 #15
2024-07-18 22:05:12 +09:30
Jon
ec1e7cca85 test: placeholder for moving organization
!42 #15
2024-07-18 22:04:27 +09:30
Jon
72ab9253d7 feat(api): When processing uploaded inventory and name does not match, update name to one within inventory file
!43
2024-07-18 17:08:52 +09:30
Jon
4f89255c4f feat(config_management): Group name to be entire breadcrumb
!43
2024-07-18 16:51:22 +09:30
8d6d1d0d56 bump: version 1.0.0-a2 → 1.0.0-a3 2024-07-18 06:25:34 +00:00
Jon
2d0c3a660a fix(config_management): dont attempt to do action during save if group being created
!42
2024-07-18 15:34:25 +09:30
Jon
974a208869 chore(config_management): remove org filter as its not required
not required as org filtering is done as part of the initial queryset within the model.

!42
2024-07-18 15:34:25 +09:30
Jon
7f225784c2 chore(settings): remove org filter as its not required
not required as org filtering is done as part of the initial queryset within the model.

!42
2024-07-18 15:34:25 +09:30
Jon
a3be95013c fix(itam): remove org filter for device so that user can see installations
not required as org filtering is done as part of the initial queryset within the model.

!42
2024-07-18 15:34:25 +09:30
Jon
adefbf3960 fix(itam): remove org filter for operating systems so that user can see installations
not required as org filtering is done as part of the initial queryset within the model.

!42
2024-07-18 15:34:25 +09:30
Jon
9a1ca7a104 fix(itam): remove org filter for software so that user can see installations
not required as org filtering is done as part of the initial queryset within the model.

!42
2024-07-18 15:34:25 +09:30
Jon
e84e80cd8f feat(config_management): Prevent a config group from being able to change organization
!42
2024-07-18 15:34:25 +09:30
Jon
ebc266010a feat(itam): On device organization change remove config groups
!42
2024-07-18 15:34:25 +09:30
Jon
519277e18b fix(itam): Device related items should not be global.
!42
2024-07-18 15:34:25 +09:30
Jon
a5a5874211 fix(itam): When changing device organization move related items too.
!42 fixes #137
2024-07-18 15:34:25 +09:30
fa2b90ee7b bump: version 1.0.0-a1 → 1.0.0-a2 2024-07-17 16:53:14 +00:00
Jon
5c74360842 fix(base): dont show user warning bar for non-authenticated user
!42
2024-07-18 01:48:57 +09:30
Jon
8457f15eca fix(api): correct inventory operating system selection by name
!42 #134
2024-07-18 01:48:21 +09:30
Jon
5bc5a4b065 docs(worker): add worker and task logs
!42 fixes #135
2024-07-18 01:28:22 +09:30
Jon
40350d166e feat(api): Inventory matching of device second by uuid
!42 #134
2024-07-18 01:17:51 +09:30
Jon
9a94ba31e4 feat(api): Inventory matching of device first by serial number
!42 #134
2024-07-18 01:17:29 +09:30
Jon
55197e7dcc fix(api): correct inventory operating system and it's linking to device
wasn't updating existing device os

!42 #134
2024-07-18 01:16:34 +09:30
Jon
a67bc70503 fix(api): correct inventory device search to be case insensitive
!42 fixes #134
2024-07-18 01:14:33 +09:30
Jon
60538e1cec feat(base): show warning bar if the user has not set a default organization
!42 fixes #133
2024-07-17 23:51:25 +09:30
Jon
416e029c23 revert: return ci build settings to not include branch alpha
partial revert of a1759ecaaf

!42
2024-07-17 11:00:41 +09:30
fe64c11927 bump: version 0.7.0 → 1.0.0-a1 2024-07-16 05:56:44 +00:00
Jon
1f8244ae40 ci: use updated commitizen
!42 !40 #74
2024-07-16 15:11:39 +09:30
Jon
9871cf248b Merge branch 'v-1-0-0-alpha' into 'development'
refactor: repo preperation for v1.0.0-Alpha-1

See merge request nofusscomputing/projects/centurion_erp!40
2024-07-16 04:24:44 +00:00
Jon
a1759ecaaf ci: add alpha branch to docker builds and publish
!40 !42 #74
2024-07-16 13:39:52 +09:30
Jon
30e0342f52 ci: temp change to release, on dev to be alpha release
!40 !35 #74
2024-07-16 13:31:18 +09:30
Jon
5a201ef548 refactor!: Squash database migrations
BREAKING CHANGE: squashed DB migrations in preparation for v1.0 release.

!40 !35 #74
2024-07-16 13:31:18 +09:30
Jon
7b26fac73d feat: Administratively set global items org/is_global field now read-only
!42 fixes #126
2024-07-16 13:19:30 +09:30
Jon
7c62309a31 fix(core): migrate manufacturer to use new form/view logic
!42 fixes #127
2024-07-16 11:55:25 +09:30
Jon
621cbd2d71 revert: return organization filtering back to forms
!42 #124
2024-07-16 00:02:45 +09:30
Jon
d8e89bee10 test: tenancy objects
!42 #15 closes #124
2024-07-15 23:22:15 +09:30
Jon
4ee62aa399 test: refactor to single abstract model for inclusion.
!42 #15
2024-07-15 23:01:49 +09:30
Jon
f1201e8933 feat(access): Add multi-tennant manager
manager filters results to that of data from the organizations the users is part of.

!42 #124
2024-07-15 16:17:08 +09:30
Jon
9acc4fdfcb docs(gitlab): update MR template
!42 #74
2024-07-14 17:16:16 +09:30
Jon
6776612b66 chore: move docker-compose to deploy directory
intent of dir is that is the location for all avail deploy methods

!42 #74
2024-07-14 17:11:49 +09:30
Jon
af3e770760 fix(settings): correct the permission to view manufacturers
!42 #74
2024-07-14 17:09:18 +09:30
Jon
fbe7e63cc9 fix(access): Correct team form fields
fixes missing name for team

!42 #74
2024-07-14 16:57:25 +09:30
Jon
aec460306b fix(config_management): don't exclude parent from field, only self
!42 #74
2024-07-14 16:48:54 +09:30
125 changed files with 1969 additions and 1805 deletions

View File

@ -1,7 +1,8 @@
---
commitizen:
name: cz_conventional_commits
prerelease_offset: 1
tag_format: $version
update_changelog_on_bump: false
version: 0.7.0
version: 1.0.0-a4
version_scheme: semver

View File

@ -115,6 +115,96 @@ Docker Container:
- when: never
.gitlab_release:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
before_script:
- if [ "0$JOB_ROOT_DIR" == "0" ]; then ROOT_DIR=gitlab-ci; else ROOT_DIR=$JOB_ROOT_DIR ; fi
- echo "[DEBUG] ROOT_DIR[$ROOT_DIR]"
- mkdir -p "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/$CI_JOB_NAME"
- mkdir -p "$CI_PROJECT_DIR/artifacts/$CI_JOB_STAGE/tests"
- apk update
- apk add git
- apk add --update --no-cache python3 && ln -sf python3 /usr/bin/python
- python -m ensurepip && ln -sf pip3 /usr/bin/pip
- pip install --upgrade pip
- pip install -r $ROOT_DIR/gitlab_release/requirements.txt
# - pip install $ROOT_DIR/gitlab_release/python-module/cz_nfc/.
- pip install commitizen --force
- 'CLONE_URL="https://gitlab-ci-token:$GIT_COMMIT_TOKEN@gitlab.com/$CI_PROJECT_PATH.git"'
- echo "[DEBUG] CLONE_URL[$CLONE_URL]"
- git clone -b development $CLONE_URL repo
- cd repo
- git branch
- git config --global user.email "helpdesk@nofusscomputing.com"
- git config --global user.name "nfc_bot"
- git push --set-upstream origin development
- RELEASE_VERSION_CURRENT=$(cz version --project)
script:
- "$MY_COMMAND"
- if [ "$CI_COMMIT_BRANCH" == "development" ] ; then RELEASE_CHANGELOG=$(cz bump --changelog --changelog-to-stdout --prerelease alpha); else RELEASE_CHANGELOG=$(cz bump --changelog --changelog-to-stdout); fi
- RELEASE_VERSION_NEW=$(cz version --project)
- RELEASE_TAG=$RELEASE_VERSION_NEW
- echo "[DEBUG] RELEASE_VERSION_CURRENT[$RELEASE_VERSION_CURRENT]"
- echo "[DEBUG] RELEASE_CHANGELOG[$RELEASE_CHANGELOG]"
- echo "[DEBUG] RELEASE_VERSION_NEW[$RELEASE_VERSION_NEW]"
- echo "[DEBUG] RELEASE_TAG[$RELEASE_TAG]"
- RELEASE_TAG_SHA1=$(git log -n1 --format=format:"%H")
- echo "[DEBUG] RELEASE_TAG_SHA1[$RELEASE_TAG_SHA1]"
- if [ "0$RELEASE_VERSION_CURRENT" == "0$RELEASE_VERSION_NEW" ]; then echo "[DEBUG] No tag to delete, version was not bumped"; else git tag -d $RELEASE_TAG; fi
- if [ "0$RELEASE_VERSION_CURRENT" == "0$RELEASE_VERSION_NEW" ]; then echo "[DEBUG] No push will be conducted, version was not bumped"; else git push; fi
- if [ "0$RELEASE_VERSION_CURRENT" == "0$RELEASE_VERSION_NEW" ]; then echo "[DEBUG] No release will be created, version was not bumped"; else release-cli create --name "Release $RELEASE_TAG" --tag-name "$RELEASE_TAG" --tag-message "$RELEASE_CHANGELOG" --ref "$RELEASE_TAG_SHA1" --description "$RELEASE_CHANGELOG"; fi
- if [ "$CI_COMMIT_BRANCH" == "master" ] ; then git checkout master; fi
- if [ "$CI_COMMIT_BRANCH" == "master" ] ; then git push --set-upstream origin master; fi
- if [ "$CI_COMMIT_BRANCH" == "master" ] ; then git merge --no-ff development; fi
- if [ "$CI_COMMIT_BRANCH" == "master" ] ; then git push origin master; fi
after_script:
- rm -Rf repo
rules:
- if: '$JOB_STOP_GITLAB_RELEASE'
when: never
- if: "$CI_COMMIT_AUTHOR =='nfc_bot <helpdesk@nofusscomputing.com>'"
when: never
- if: # condition_master_branch_push
$CI_COMMIT_BRANCH == "master" &&
$CI_PIPELINE_SOURCE == "push"
allow_failure: false
when: on_success
- if: # condition_dev_branch_push
$CI_COMMIT_BRANCH == "development" &&
$CI_PIPELINE_SOURCE == "push"
when: manual
allow_failure: true
# for testing
# - if: '$CI_COMMIT_BRANCH != "master"'
# when: always
# allow_failure: true
- when: never
#
# Release
#
Gitlab Release:
extends:
- .gitlab_release
Docker.Hub.Branch.Publish:
extends: .publish-docker-hub
needs: [ "Docker Container" ]
@ -162,13 +252,8 @@ Github (Push --mirror):
when: never
- if: # condition_master_or_dev_push
(
$CI_COMMIT_BRANCH == "master"
||
$CI_COMMIT_BRANCH == "development"
||
$CI_COMMIT_BRANCH == "14-feat-project-management"
) &&
$CI_COMMIT_BRANCH
&&
$CI_PIPELINE_SOURCE == "push"
when: always

View File

@ -20,7 +20,7 @@
<!-- dont remove tasks below strike through including the checkbox by enclosing in double tidle '~~' -->
- [ ] ~"breaking-change" Any Breaking change(s)
- [ ] Contains ~"breaking-change" Any Breaking change(s)?
_Breaking Change must also be notated in the commit that introduces it and in [Conventional Commit Format](https://www.conventionalcommits.org/en/v1.0.0/)._
@ -30,6 +30,6 @@
- [ ] Milestone assigned
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/django-template/development/testing/)
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
_ensure test coverage delta is not less than zero_

View File

@ -1,3 +1,63 @@
## 1.0.0-a4 (2024-07-18)
### Feat
- **api**: When processing uploaded inventory and name does not match, update name to one within inventory file
- **config_management**: Group name to be entire breadcrumb
## 1.0.0-a3 (2024-07-18)
### Feat
- **config_management**: Prevent a config group from being able to change organization
- **itam**: On device organization change remove config groups
### Fix
- **config_management**: dont attempt to do action during save if group being created
- **itam**: remove org filter for device so that user can see installations
- **itam**: remove org filter for operating systems so that user can see installations
- **itam**: remove org filter for software so that user can see installations
- **itam**: Device related items should not be global.
- **itam**: When changing device organization move related items too.
## 1.0.0-a2 (2024-07-17)
### Feat
- **api**: Inventory matching of device second by uuid
- **api**: Inventory matching of device first by serial number
- **base**: show warning bar if the user has not set a default organization
### Fix
- **base**: dont show user warning bar for non-authenticated user
- **api**: correct inventory operating system selection by name
- **api**: correct inventory operating system and it's linking to device
- **api**: correct inventory device search to be case insensitive
## 1.0.0-a1 (2024-07-16)
### BREAKING CHANGE
- squashed DB migrations in preparation for v1.0 release.
### Feat
- Administratively set global items org/is_global field now read-only
- **access**: Add multi-tennant manager
### Fix
- **core**: migrate manufacturer to use new form/view logic
- **settings**: correct the permission to view manufacturers
- **access**: Correct team form fields
- **config_management**: don't exclude parent from field, only self
### Refactor
- Squash database migrations
## 0.7.0 (2024-07-14)
### Bug Fixes

View File

@ -28,7 +28,8 @@ class TeamFormAdd(CommonModelForm):
class Meta:
model = Team
fields = [
'name',
'team_name',
'model_notes',
]
@ -38,7 +39,7 @@ class TeamForm(CommonModelForm):
class Meta:
model = Team
fields = [
'name',
'team_name',
'permissions',
'model_notes',
]

View File

@ -1,6 +1,7 @@
# Generated by Django 5.0.4 on 2024-05-13 16:08
# Generated by Django 5.0.7 on 2024-07-12 03:54
import access.fields
import access.models
import django.contrib.auth.models
import django.db.models.deletion
import django.utils.timezone
@ -23,9 +24,11 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50, unique=True)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('slug', access.fields.AutoSlugField()),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
('manager', models.ForeignKey(help_text='Organization Manager', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name_plural': 'Organizations',
@ -37,10 +40,11 @@ class Migration(migrations.Migration):
fields=[
('group_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='auth.group')),
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('team_name', models.CharField(default='', max_length=50, verbose_name='Name')),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
],
options={
'verbose_name_plural': 'Teams',

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 10:37
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='team',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-05 09:16
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0002_alter_team_organization'),
]
operations = [
migrations.AlterField(
model_name='team',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-11 20:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_team_organization'),
]
operations = [
migrations.AddField(
model_name='team',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-17 10:03
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0004_team_model_notes'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AddField(
model_name='organization',
name='manager',
field=models.ForeignKey(help_text='Organization Manager', null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='organization',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-11 04:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0005_organization_manager_organization_model_notes'),
]
operations = [
migrations.AlterField(
model_name='organization',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='team',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
]

View File

@ -80,7 +80,9 @@ class OrganizationMixin():
id = int(self.request.POST.get(field))
except:
pass
return id

View File

@ -63,13 +63,103 @@ class Organization(SaveHistory):
return self
class TenancyObject(models.Model):
class TenancyManager(models.Manager):
"""Multi-Tennant Object Manager
This manager specifically caters for the multi-tenancy features of Centurion ERP.
"""
def get_queryset(self):
""" Fetch the data
This function filters the data fetched from the database to that which is from the organizations
the user is a part of.
!!! danger "Requirement"
This method may be overridden however must still be called from the overriding function. i.e. `super().get_queryset()`
## Workflow
This functions workflow is as follows:
- Fetch the user from the request
- Check if the user is authenticated
- Iterate over the users teams
- Store unique organizations from users teams
- return results
Returns:
(queryset): **super user**: return unfiltered data.
(queryset): **not super user**: return data from the stored unique organizations.
"""
request = get_request()
user_organizations: list(str()) = []
if request:
user = request.user._wrapped if hasattr(request.user,'_wrapped') else request.user
if user.is_authenticated:
for team_user in TeamUsers.objects.filter(user=user):
if team_user.team.organization.name not in user_organizations:
if not user_organizations:
self.user_organizations = []
user_organizations += [ team_user.team.organization.id ]
if len(user_organizations) > 0 and not user.is_superuser:
return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
|
models.Q(is_global = True)
)
return super().get_queryset()
class TenancyObject(SaveHistory):
""" Tenancy Model Abstrct class.
This class is for inclusion wihtin **every** model within Centurion ERP.
Provides the required fields, functions and methods for multi tennant objects.
Unless otherwise stated, **no** object within this class may be overridden.
Raises:
ValidationError: User failed to supply organization
"""
objects = TenancyManager()
""" Multi-Tenanant Objects """
class Meta:
abstract = True
def validatate_organization_exists(self):
"""Ensure that the user did provide an organization
Raises:
ValidationError: User failed to supply organization.
"""
if not self:
raise ValidationError('You must provide an organization')
@ -99,6 +189,7 @@ class TenancyObject(models.Model):
return self.organization
class Team(Group, TenancyObject, SaveHistory):
class Meta:
# proxy = True

View File

@ -1,6 +1,8 @@
import pytest
import unittest
from access.models import TenancyManager
class TenancyObject:
@ -66,3 +68,21 @@ class TenancyObject:
Must not be able to edit an item without an organization
"""
pass
def test_has_attr_organization(self):
""" TenancyObject attribute check
TenancyObject has function objects
"""
assert hasattr(self.model, 'objects')
def test_attribute_is_type_objects(self):
""" Attribute Check
attribute `objects` must be set to `access.models.TenancyManager()`
"""
assert type(self.model.objects) is TenancyManager

View File

@ -5,9 +5,14 @@ from django.test import TestCase, Client
from access.models import Organization, Team, TeamUsers, Permission
from app.tests.abstract.models import TenancyModel
class TeamModel(TestCase):
class TeamModel(
TestCase,
TenancyModel
):
model = Team
@ -53,4 +58,9 @@ class TeamModel(TestCase):
the save method is overridden. the function attributes must match default django method
"""
pass
@pytest.mark.skip(reason="uses Django group manager")
def test_attribute_is_type_objects(self):
pass

View File

@ -1,17 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.models import Team
from access.tests.abstract.tenancy_object import TenancyObject
class TeamTenancyObject(
TestCase,
TenancyObject
):
model = Team

View File

@ -3,17 +3,45 @@ import unittest
from django.test import TestCase
from access.models import TenancyObject
from access.models import TenancyObject, TenancyManager
from core.mixin.history_save import SaveHistory
from unittest.mock import patch
class TenancyObject(TestCase):
class TenancyManagerTests(TestCase):
item = TenancyManager
def test_has_attribute_get_queryset(self):
""" Field organization exists """
assert hasattr(self.item, 'get_queryset')
def test_is_function_get_queryset(self):
""" Attribute 'get_organization' is a function """
assert callable(self.item.get_queryset)
class TenancyObjectTests(TestCase):
item = TenancyObject
def test_class_inherits_save_history(self):
""" Confirm class inheritence
TenancyObject must inherit SaveHistory
"""
assert issubclass(TenancyObject, SaveHistory)
def test_has_attribute_organization(self):
""" Field organization exists """
@ -43,3 +71,23 @@ class TenancyObject(TestCase):
""" Attribute 'get_organization' is a function """
assert callable(self.item.get_organization)
@pytest.mark.skip(reason="figure out how to test abstract class")
def test_has_attribute_objects(self):
""" Attribute Check
attribute `objects` must be set to `access.models.TenancyManager()`
"""
assert 'objects' in self.item
@pytest.mark.skip(reason="figure out how to test abstract class")
def test_attribute_not_none_objects(self):
""" Attribute Check
attribute `objects` must be set to `access.models.TenancyManager()`
"""
assert self.item.objects is not None

View File

@ -1,4 +1,4 @@
# Generated by Django 5.0.6 on 2024-06-27 18:25
# Generated by Django 5.0.7 on 2024-07-12 03:54
import access.fields
import django.db.models.deletion

View File

@ -37,13 +37,6 @@ def process_inventory(self, data, organization: int):
organization = Organization.objects.get(id=organization)
if Device.objects.filter(slug=str(data.details.name).lower()).exists():
device = Device.objects.get(slug=str(data.details.name).lower())
# device = self.obj
app_settings = AppSettings.objects.get(owner_organization = None)
device_serial_number = None
@ -57,10 +50,62 @@ def process_inventory(self, data, organization: int):
device_uuid = str(data.details.uuid)
if device_serial_number: # Search for device by serial number.
device = Device.objects.filter(
serial_number__iexact=device_serial_number
)
if device.exists():
device = Device.objects.get(
serial_number__iexact=device_serial_number
)
else:
device = None
if device_uuid and not device: # Search for device by UUID.
device = Device.objects.filter(
uuid__iexact=device_uuid
)
if device.exists():
device = Device.objects.get(
uuid__iexact=device_uuid
)
else:
device = None
if not device: # Search for device by Name.
device = Device.objects.filter(
name__iexact=str(data.details.name).lower()
)
if device.exists():
device = Device.objects.get(
name__iexact=str(data.details.name).lower()
)
else:
device = None
if not device: # Create the device
device = Device.objects.create(
name = data.details.name,
device_type = None,
@ -74,25 +119,75 @@ def process_inventory(self, data, organization: int):
logger.info(f"Device: {device.name}, Serial: {device.serial_number}, UUID: {device.uuid}")
device_edited = False
if not device.uuid and device_uuid:
device.uuid = device_uuid
device.save()
device_edited = True
if not device.serial_number and device_serial_number:
device.serial_number = data.details.serial_number
device_edited = True
if str(device.name).lower() != str(data.details.name).lower(): # Update device Name
device.name = data.details.name
device_edited = True
if device_edited:
device.save()
if OperatingSystem.objects.filter( slug=data.operating_system.name ).exists():
operating_system = OperatingSystem.objects.filter(
name=data.operating_system.name,
is_global = True
)
operating_system = OperatingSystem.objects.get( slug=data.operating_system.name )
if operating_system.exists():
else: # Create Operating System
operating_system = OperatingSystem.objects.get(
name=data.operating_system.name,
is_global = True
)
else:
operating_system = None
if not operating_system:
operating_system = OperatingSystem.objects.filter(
name=data.operating_system.name,
organization = organization
)
if operating_system.exists():
operating_system = OperatingSystem.objects.get(
name=data.operating_system.name,
organization = organization
)
else:
operating_system = None
if not operating_system:
operating_system = OperatingSystem.objects.create(
name = data.operating_system.name,
@ -101,16 +196,24 @@ def process_inventory(self, data, organization: int):
)
if OperatingSystemVersion.objects.filter( name=data.operating_system.version_major, operating_system=operating_system ).exists():
operating_system_version = OperatingSystemVersion.objects.filter(
name=data.operating_system.version_major,
operating_system=operating_system
)
if operating_system_version.exists():
operating_system_version = OperatingSystemVersion.objects.get(
organization = organization,
is_global = True,
name = data.operating_system.version_major,
operating_system = operating_system
name=data.operating_system.version_major,
operating_system=operating_system
)
else: # Create Operating System Version
else:
operating_system_version = None
if not operating_system_version:
operating_system_version = OperatingSystemVersion.objects.create(
organization = organization,
@ -119,22 +222,22 @@ def process_inventory(self, data, organization: int):
operating_system = operating_system,
)
device_operating_system = DeviceOperatingSystem.objects.filter(
device=device,
)
if DeviceOperatingSystem.objects.filter( version=data.operating_system.version, device=device, operating_system_version=operating_system_version ).exists():
if device_operating_system.exists():
device_operating_system = DeviceOperatingSystem.objects.get(
device=device,
version = data.operating_system.version,
operating_system_version = operating_system_version,
)
if not device_operating_system.installdate: # Only update install date if empty
else:
device_operating_system.installdate = timezone.now()
device_operating_system = None
device_operating_system.save()
else: # Create Operating System Version
if not device_operating_system:
device_operating_system = DeviceOperatingSystem.objects.create(
organization = organization,
@ -144,6 +247,26 @@ def process_inventory(self, data, organization: int):
installdate = timezone.now()
)
if not device_operating_system.installdate: # Only update install date if empty
device_operating_system.installdate = timezone.now()
device_operating_system.save()
if device_operating_system.operating_system_version != operating_system_version:
device_operating_system.operating_system_version = operating_system_version
device_operating_system.save()
if device_operating_system.version != data.operating_system.version:
device_operating_system.version = data.operating_system.version
device_operating_system.save()
if app_settings.software_is_global:

View File

@ -255,6 +255,20 @@ class InventoryAPI(TestCase):
def test_api_inventory_device_uuid_match(self):
""" Device uuid match """
assert self.device.uuid == self.inventory['details']['uuid']
def test_api_inventory_device_serial_number_match(self):
""" Device SN match """
assert self.device.serial_number == self.inventory['details']['serial_number']
def test_api_inventory_operating_system_added(self):
""" Operating System is created """
@ -424,3 +438,552 @@ class InventoryAPI(TestCase):
"""
pass
class InventoryAPIDifferentNameSerialNumberMatch(TestCase):
""" Test inventory upload with different name
should match by serial number
"""
model = Device
model_name = 'device'
app_label = 'itam'
inventory = {
"details": {
"name": "device_name",
"serial_number": "serial_number_123",
"uuid": "string"
},
"os": {
"name": "os_name",
"version_major": "12",
"version": "12.1"
},
"software": [
{
"name": "software_name",
"category": "category_name",
"version": "1.2.3"
},
{
"name": "software_name_not_semver",
"category": "category_name",
"version": "2024.4"
},
{
"name": "software_name_semver_contained",
"category": "category_name",
"version": "1.2.3-rc1"
},
]
}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user
2. Create a team for user with correct permissions
3. add user to the teeam
4. upload the inventory
5. conduct queries for tests
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
Device.objects.create(
name='random device name',
serial_number='serial_number_123'
)
add_permissions = Permission.objects.get(
codename = 'add_' + self.model_name,
content_type = ContentType.objects.get(
app_label = self.app_label,
model = self.model_name,
)
)
add_team = Team.objects.create(
team_name = 'add_team',
organization = organization,
)
add_team.permissions.set([add_permissions])
self.add_user = User.objects.create_user(username="test_user_add", password="password")
add_user_settings = UserSettings.objects.get(user=self.add_user)
add_user_settings.default_organization = organization
add_user_settings.save()
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
# upload the inventory
process_inventory(json.dumps(self.inventory), organization.id)
self.device = Device.objects.get(name=self.inventory['details']['name'])
self.operating_system = OperatingSystem.objects.get(name=self.inventory['os']['name'])
self.operating_system_version = OperatingSystemVersion.objects.get(name=self.inventory['os']['version_major'])
self.device_operating_system = DeviceOperatingSystem.objects.get(version=self.inventory['os']['version'])
self.software = Software.objects.get(name=self.inventory['software'][0]['name'])
self.software_category = SoftwareCategory.objects.get(name=self.inventory['software'][0]['category'])
self.software_version = SoftwareVersion.objects.get(
name = self.inventory['software'][0]['version'],
software = self.software,
)
self.software_not_semver = Software.objects.get(name=self.inventory['software'][1]['name'])
self.software_version_not_semver = SoftwareVersion.objects.get(
name = self.inventory['software'][1]['version'],
software = self.software_not_semver
)
self.software_is_semver = Software.objects.get(name=self.inventory['software'][2]['name'])
self.software_version_is_semver = SoftwareVersion.objects.get(
software = self.software_is_semver
)
self.device_software = DeviceSoftware.objects.get(device=self.device,software=self.software)
def test_api_inventory_device_added(self):
""" Device is created """
assert self.device.name == self.inventory['details']['name']
def test_api_inventory_device_uuid_match(self):
""" Device uuid match """
assert self.device.uuid == self.inventory['details']['uuid']
def test_api_inventory_device_serial_number_match(self):
""" Device SN match """
assert self.device.serial_number == self.inventory['details']['serial_number']
def test_api_inventory_operating_system_added(self):
""" Operating System is created """
assert self.operating_system.name == self.inventory['os']['name']
def test_api_inventory_operating_system_version_added(self):
""" Operating System version is created """
assert self.operating_system_version.name == self.inventory['os']['version_major']
def test_api_inventory_device_has_operating_system_added(self):
""" Operating System version linked to device """
assert self.device_operating_system.version == self.inventory['os']['version']
@pytest.mark.skip(reason="to be written")
def test_api_inventory_device_operating_system_version_is_semver(self):
""" Operating System version is full semver
Operating system versions name is the major version number of semver.
The device version is to be full semver
"""
pass
@pytest.mark.skip(reason="to be written")
def test_api_inventory_software_no_version_cleaned(self):
""" Check softare cleaned up
As part of the inventory upload the software versions of software found on the device is set to null
and before the processing is completed, the version=null software is supposed to be cleaned up.
"""
pass
def test_api_inventory_software_category_added(self):
""" Software category exists """
assert self.software_category.name == self.inventory['software'][0]['category']
def test_api_inventory_software_added(self):
""" Test software exists """
assert self.software.name == self.inventory['software'][0]['name']
def test_api_inventory_software_category_linked_to_software(self):
""" Software category linked to software """
assert self.software.category == self.software_category
def test_api_inventory_software_version_added(self):
""" Test software version exists """
assert self.software_version.name == self.inventory['software'][0]['version']
def test_api_inventory_software_version_returns_semver(self):
""" Software Version from inventory returns semver if within version string """
assert self.software_version_is_semver.name == str(self.inventory['software'][2]['version']).split('-')[0]
def test_api_inventory_software_version_returns_original_version(self):
""" Software Version from inventory returns inventoried version if no semver found """
assert self.software_version_not_semver.name == self.inventory['software'][1]['version']
def test_api_inventory_software_version_linked_to_software(self):
""" Test software version linked to software it belongs too """
assert self.software_version.software == self.software
def test_api_inventory_device_has_software_version(self):
""" Inventoried software is linked to device and it's the corret one"""
assert self.software_version.name == self.inventory['software'][0]['version']
def test_api_inventory_device_software_has_installed_date(self):
""" Inventoried software version has install date """
assert self.device_software.installed is not None
def test_api_inventory_device_software_installed_date_type(self):
""" Inventoried software version has install date """
assert type(self.device_software.installed) is datetime.datetime
@pytest.mark.skip(reason="to be written")
def test_api_inventory_device_software_blank_installed_date_is_updated(self):
""" A blank installed date of software is updated if the software was already attached to the device """
pass
class InventoryAPIDifferentNameUUIDMatch(TestCase):
""" Test inventory upload with different name
should match by uuid
"""
model = Device
model_name = 'device'
app_label = 'itam'
inventory = {
"details": {
"name": "device_name",
"serial_number": "serial_number_123",
"uuid": "123-456-789"
},
"os": {
"name": "os_name",
"version_major": "12",
"version": "12.1"
},
"software": [
{
"name": "software_name",
"category": "category_name",
"version": "1.2.3"
},
{
"name": "software_name_not_semver",
"category": "category_name",
"version": "2024.4"
},
{
"name": "software_name_semver_contained",
"category": "category_name",
"version": "1.2.3-rc1"
},
]
}
@classmethod
def setUpTestData(self):
"""Setup Test
1. Create an organization for user
2. Create a team for user with correct permissions
3. add user to the teeam
4. upload the inventory
5. conduct queries for tests
"""
organization = Organization.objects.create(name='test_org')
self.organization = organization
Device.objects.create(
name='random device name',
uuid='123-456-789'
)
add_permissions = Permission.objects.get(
codename = 'add_' + self.model_name,
content_type = ContentType.objects.get(
app_label = self.app_label,
model = self.model_name,
)
)
add_team = Team.objects.create(
team_name = 'add_team',
organization = organization,
)
add_team.permissions.set([add_permissions])
self.add_user = User.objects.create_user(username="test_user_add", password="password")
add_user_settings = UserSettings.objects.get(user=self.add_user)
add_user_settings.default_organization = organization
add_user_settings.save()
teamuser = TeamUsers.objects.create(
team = add_team,
user = self.add_user
)
# upload the inventory
process_inventory(json.dumps(self.inventory), organization.id)
self.device = Device.objects.get(name=self.inventory['details']['name'])
self.operating_system = OperatingSystem.objects.get(name=self.inventory['os']['name'])
self.operating_system_version = OperatingSystemVersion.objects.get(name=self.inventory['os']['version_major'])
self.device_operating_system = DeviceOperatingSystem.objects.get(version=self.inventory['os']['version'])
self.software = Software.objects.get(name=self.inventory['software'][0]['name'])
self.software_category = SoftwareCategory.objects.get(name=self.inventory['software'][0]['category'])
self.software_version = SoftwareVersion.objects.get(
name = self.inventory['software'][0]['version'],
software = self.software,
)
self.software_not_semver = Software.objects.get(name=self.inventory['software'][1]['name'])
self.software_version_not_semver = SoftwareVersion.objects.get(
name = self.inventory['software'][1]['version'],
software = self.software_not_semver
)
self.software_is_semver = Software.objects.get(name=self.inventory['software'][2]['name'])
self.software_version_is_semver = SoftwareVersion.objects.get(
software = self.software_is_semver
)
self.device_software = DeviceSoftware.objects.get(device=self.device,software=self.software)
def test_api_inventory_device_added(self):
""" Device is created """
assert self.device.name == self.inventory['details']['name']
def test_api_inventory_device_uuid_match(self):
""" Device uuid match """
assert self.device.uuid == self.inventory['details']['uuid']
def test_api_inventory_device_serial_number_match(self):
""" Device SN match """
assert self.device.serial_number == self.inventory['details']['serial_number']
def test_api_inventory_operating_system_added(self):
""" Operating System is created """
assert self.operating_system.name == self.inventory['os']['name']
def test_api_inventory_operating_system_version_added(self):
""" Operating System version is created """
assert self.operating_system_version.name == self.inventory['os']['version_major']
def test_api_inventory_device_has_operating_system_added(self):
""" Operating System version linked to device """
assert self.device_operating_system.version == self.inventory['os']['version']
@pytest.mark.skip(reason="to be written")
def test_api_inventory_device_operating_system_version_is_semver(self):
""" Operating System version is full semver
Operating system versions name is the major version number of semver.
The device version is to be full semver
"""
pass
@pytest.mark.skip(reason="to be written")
def test_api_inventory_software_no_version_cleaned(self):
""" Check softare cleaned up
As part of the inventory upload the software versions of software found on the device is set to null
and before the processing is completed, the version=null software is supposed to be cleaned up.
"""
pass
def test_api_inventory_software_category_added(self):
""" Software category exists """
assert self.software_category.name == self.inventory['software'][0]['category']
def test_api_inventory_software_added(self):
""" Test software exists """
assert self.software.name == self.inventory['software'][0]['name']
def test_api_inventory_software_category_linked_to_software(self):
""" Software category linked to software """
assert self.software.category == self.software_category
def test_api_inventory_software_version_added(self):
""" Test software version exists """
assert self.software_version.name == self.inventory['software'][0]['version']
def test_api_inventory_software_version_returns_semver(self):
""" Software Version from inventory returns semver if within version string """
assert self.software_version_is_semver.name == str(self.inventory['software'][2]['version']).split('-')[0]
def test_api_inventory_software_version_returns_original_version(self):
""" Software Version from inventory returns inventoried version if no semver found """
assert self.software_version_not_semver.name == self.inventory['software'][1]['version']
def test_api_inventory_software_version_linked_to_software(self):
""" Test software version linked to software it belongs too """
assert self.software_version.software == self.software
def test_api_inventory_device_has_software_version(self):
""" Inventoried software is linked to device and it's the corret one"""
assert self.software_version.name == self.inventory['software'][0]['version']
def test_api_inventory_device_software_has_installed_date(self):
""" Inventoried software version has install date """
assert self.device_software.installed is not None
def test_api_inventory_device_software_installed_date_type(self):
""" Inventoried software version has install date """
assert type(self.device_software.installed) is datetime.datetime
@pytest.mark.skip(reason="to be written")
def test_api_inventory_device_software_blank_installed_date_is_updated(self):
""" A blank installed date of software is updated if the software was already attached to the device """
pass

View File

@ -74,6 +74,23 @@ def user_settings(context) -> int:
return None
def user_default_organization(context) -> int:
""" Provides the users default organization.
Returns:
int: Users Default Organization
"""
if context.user.is_authenticated:
settings = UserSettings.objects.filter(user=context.user)
if settings[0].default_organization:
return settings[0].default_organization.id
return None
def nav_items(context) -> list(dict()):
""" Fetch All Project URLs
@ -203,4 +220,5 @@ def common(context):
'nav_items': nav_items(context),
'social_backends': social_backends(context),
'user_settings': user_settings(context),
'user_default_organization': user_default_organization(context)
}

View File

@ -1,8 +1,44 @@
import pytest
import unittest
from access.models import TenancyObject
from access.tests.abstract.tenancy_object import TenancyObject as TenancyObjectTestCases
from app.tests.abstract.views import AddView, ChangeView, DeleteView, DisplayView, IndexView
from core.mixin.history_save import SaveHistory
class BaseModel:
""" Test cases for all models """
model = None
""" Model to test """
@pytest.mark.skip(reason="figure out how to test sub-sub-class")
def test_class_inherits_save_history(self):
""" Confirm class inheritence
TenancyObject must inherit SaveHistory
"""
assert issubclass(self.model, TenancyObject)
class TenancyModel(
BaseModel,
TenancyObjectTestCases
):
""" Test cases for tenancy models"""
model = None
""" Model to test """
class ModelAdd(
AddView
):

View File

@ -26,9 +26,9 @@ class ConfigGroupForm(CommonModelForm):
super().__init__(*args, **kwargs)
if 'parent' in kwargs['initial']:
if hasattr(kwargs['instance'], 'id'):
self.fields['parent'].queryset = self.fields['parent'].queryset.filter(
).exclude(
id=int(kwargs['initial']['parent'])
id=int(kwargs['instance'].id)
)

View File

@ -1,6 +1,8 @@
# Generated by Django 5.0.6 on 2024-06-02 14:48
# Generated by Django 5.0.7 on 2024-07-12 03:54
import access.fields
import access.models
import config_management.models.groups
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
@ -11,7 +13,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('access', '0002_alter_team_organization'),
('access', '0001_initial'),
]
operations = [
@ -19,16 +21,17 @@ class Migration(migrations.Migration):
name='ConfigGroups',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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')),
('config', models.JSONField(blank=True, default=None, null=True, validators=[config_management.models.groups.ConfigGroups.validate_config_keys_not_reserved])),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('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',
'abstract': False,
},
),
]

View File

@ -1,43 +0,0 @@
# 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_not_reserved]),
),
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,
},
),
]

View File

@ -1,7 +1,8 @@
# Generated by Django 5.0.6 on 2024-06-07 21:43
# Generated by Django 5.0.7 on 2024-07-12 03:58
import access.fields
import access.models
import config_management.models.groups
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
@ -10,16 +11,33 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_team_organization'),
('config_management', '0003_alter_configgrouphosts_organization_and_more'),
('itam', '0013_alter_device_organization_and_more'),
('access', '0001_initial'),
('config_management', '0001_initial'),
('itam', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ConfigGroupHosts',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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', validators=[access.models.TenancyObject.validatate_organization_exists])),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='ConfigGroupSoftware',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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)),

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-05 09:16
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_team_organization'),
('config_management', '0002_alter_configgroups_options_alter_configgroups_config_and_more'),
]
operations = [
migrations.AlterField(
model_name='configgrouphosts',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='configgroups',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-11 20:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config_management', '0004_configgroupsoftware'),
]
operations = [
migrations.AddField(
model_name='configgrouphosts',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='configgroups',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='configgroupsoftware',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-11 04:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('config_management', '0005_configgrouphosts_model_notes_and_more'),
]
operations = [
migrations.AlterField(
model_name='configgrouphosts',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='configgroups',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='configgroupsoftware',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
]

View File

@ -186,6 +186,15 @@ class ConfigGroups(GroupsCommonFields, SaveHistory):
if self.parent:
self.organization = ConfigGroups.objects.get(id=self.parent.id).organization
if self.pk:
obj = ConfigGroups.objects.get(
id = self.id,
)
# Prevent organization change. ToDo: add feature so that config can change organizations
self.organization = obj.organization
super().save(*args, **kwargs)
@ -193,7 +202,7 @@ class ConfigGroups(GroupsCommonFields, SaveHistory):
if self.parent:
return f'{self.parent.name} > {self.name}'
return f'{self.parent} > {self.name}'
return self.name

View File

@ -5,12 +5,17 @@ from django.test import TestCase
from access.models import Organization
from app.tests.abstract.models import TenancyModel
from config_management.models.groups import ConfigGroups
@pytest.mark.django_db
class ConfigGroupsModel(TestCase):
class ConfigGroupsModel(
TestCase,
TenancyModel
):
model = ConfigGroups

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from config_management.models.groups import ConfigGroups
class ConfigGroupsTenancyObject(
TestCase,
TenancyObject
):
model = ConfigGroups

View File

@ -5,6 +5,8 @@ from django.test import TestCase
from access.models import Organization
from app.tests.abstract.models import TenancyModel
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
from itam.models.device import DeviceSoftware
@ -12,7 +14,10 @@ from itam.models.software import Software
class ConfigGroupSoftwareModel(TestCase):
class ConfigGroupSoftwareModel(
TestCase,
TenancyModel
):
model = ConfigGroupSoftware

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from config_management.models.groups import ConfigGroupSoftware
class ConfigGroupSoftwareTenancyObject(
TestCase,
TenancyObject
):
model = ConfigGroupSoftware

View File

@ -1,7 +1,6 @@
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
@ -47,13 +46,7 @@ class GroupIndexView(IndexView):
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')
return self.model.objects.filter(parent=None).order_by('name')

View File

@ -1,5 +1,4 @@
from django import forms
from django.db.models import Q
from access.models import Organization, TeamUsers
@ -27,12 +26,15 @@ class CommonModelForm(forms.ModelForm):
Initialize the form using the super classes first then continue to initialize the form using logic
contained within this method.
## Tenancy Objects
Fields that contain an attribute called `organization` will have the objects filtered to
the organizations the user is part of. If the object has `is_global=True`, that object will not be
filtered out.
!!! danger "Requirement"
This method may be overridden however must still be called from the overriding function. i.e. `super().__init__(*args, **kwargs)`
"""
user = kwargs.pop('user', None)
@ -92,9 +94,7 @@ class CommonModelForm(forms.ModelForm):
if self.organization_field in self.Meta.fields:
self.fields[self.organization_field].queryset = self.fields[self.organization_field].queryset.filter(
Q(name__in=user_organizations)
Q(id__in=user_organizations_id)
|
Q(manager=user)
)

View File

@ -1,10 +1,16 @@
from django import forms
from core.forms.common import CommonModelForm
from core.models.manufacturer import Manufacturer
from settings.forms.admin_settings_global import AdminGlobalModels
class ManufacturerForm(forms.ModelForm):
class ManufacturerForm(
AdminGlobalModels,
CommonModelForm
):
class Meta:

View File

@ -1,6 +1,7 @@
# Generated by Django 5.0.6 on 2024-05-20 15:44
# Generated by Django 5.0.7 on 2024-07-12 03:54
import access.fields
import access.models
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
@ -13,29 +14,43 @@ class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
('itam', '0007_device_inventorydate'),
('config_management', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Notes',
name='History',
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)),
('serial_number', models.CharField(blank=True, default=None, max_length=50, null=True, verbose_name='Serial Number')),
('note', models.TextField(default=None, null=True, verbose_name='Note')),
('device', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='itam.device')),
('operatingsystem', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='itam.operatingsystem')),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('software', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='itam.software')),
('usercreated', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='usercreated', to=settings.AUTH_USER_MODEL, verbose_name='Added By')),
('usermodified', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='usermodified', to=settings.AUTH_USER_MODEL, verbose_name='Edited By')),
('before', models.TextField(blank=True, default=None, help_text='JSON Object before Change', null=True)),
('after', models.TextField(blank=True, default=None, help_text='JSON Object After Change', null=True)),
('action', models.IntegerField(choices=[('1', 'Create'), ('2', 'Update'), ('3', 'Delete')], default=None, null=True)),
('item_pk', models.IntegerField(default=None, null=True)),
('item_class', models.CharField(default=None, max_length=50, null=True)),
('item_parent_pk', models.IntegerField(default=None, null=True)),
('item_parent_class', models.CharField(default=None, max_length=50, null=True)),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created'],
},
),
migrations.CreateModel(
name='Manufacturer',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('name', models.CharField(max_length=50, unique=True)),
('slug', access.fields.AutoSlugField()),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
],
options={
'ordering': ['name'],
},
),
]

View File

@ -0,0 +1,43 @@
# Generated by Django 5.0.7 on 2024-07-12 03:58
import access.fields
import access.models
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
('config_management', '0002_configgrouphosts_configgroupsoftware'),
('core', '0001_initial'),
('itam', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Notes',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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)),
('note', models.TextField(blank=True, default=None, null=True, verbose_name='Note')),
('config_group', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='config_management.configgroups')),
('device', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.device')),
('operatingsystem', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('software', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.software')),
('usercreated', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='usercreated', to=settings.AUTH_USER_MODEL, verbose_name='Added By')),
('usermodified', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='usermodified', to=settings.AUTH_USER_MODEL, verbose_name='Edited By')),
],
options={
'ordering': ['-created'],
},
),
]

View File

@ -1,34 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-20 16:06
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
('itam', '0007_device_inventorydate'),
]
operations = [
migrations.RemoveField(
model_name='notes',
name='serial_number',
),
migrations.AlterField(
model_name='notes',
name='device',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.device'),
),
migrations.AlterField(
model_name='notes',
name='operatingsystem',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem'),
),
migrations.AlterField(
model_name='notes',
name='software',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.software'),
),
]

View File

@ -1,41 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 03:59
import access.fields
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0002_remove_notes_serial_number_alter_notes_device_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='notes',
name='note',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Note'),
),
migrations.CreateModel(
name='History',
fields=[
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('before', models.TextField(blank=True, default=None, help_text='JSON Object before Change', null=True)),
('after', models.TextField(blank=True, default=None, help_text='JSON Object After Change', null=True)),
('action', models.IntegerField(choices=[('1', 'Create'), ('2', 'Update'), ('3', 'Delete')], default=None, null=True)),
('item_pk', models.IntegerField(default=None, null=True)),
('item_class', models.CharField(default=None, max_length=50, null=True)),
('item_parent_pk', models.IntegerField(default=None, null=True)),
('item_parent_class', models.CharField(default=None, max_length=50, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created'],
},
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 10:37
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0002_alter_team_organization'),
('core', '0003_alter_notes_note_history'),
]
operations = [
migrations.AlterField(
model_name='notes',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
]

View File

@ -1,32 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 10:58
import access.fields
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'),
('core', '0004_notes_is_null'),
]
operations = [
migrations.CreateModel(
name='Manufacturer',
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.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('name', models.CharField(max_length=50, unique=True)),
('slug', access.fields.AutoSlugField()),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
],
options={
'ordering': ['name'],
},
),
]

View File

@ -1,21 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-25 05:21
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_manufacturer'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.AlterField(
model_name='history',
name='user',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
),
]

View File

@ -1,20 +0,0 @@
# 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'),
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-05 09:16
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_team_organization'),
('core', '0007_notes_config_group'),
]
operations = [
migrations.AlterField(
model_name='manufacturer',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='notes',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-11 20:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_alter_manufacturer_organization_and_more'),
]
operations = [
migrations.AddField(
model_name='manufacturer',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='notes',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-11 04:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0009_manufacturer_model_notes_notes_model_notes'),
]
operations = [
migrations.AlterField(
model_name='manufacturer',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='notes',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
]

View File

@ -0,0 +1,43 @@
import pytest
import unittest
from django.test import TestCase
class Models:
""" Test cases for Model Abstract Classes """
@pytest.mark.skip(reason="write test")
def test_model_class_tenancy_manager_function_get_queryset(self):
""" Function Check
function `get_queryset()` must exist
"""
pass
@pytest.mark.skip(reason="write test")
def test_model_class_tenancy_manager_results_get_queryset(self):
""" Function Results Check
function `get_queryset()` must not return data from any organization the user is
not part of.
"""
pass
@pytest.mark.skip(reason="write test")
def test_model_class_tenancy_manager_results_get_queryset_super_user(self):
""" Function Results Check
function `get_queryset()` must return un-filtered data for super-user.
"""
pass

View File

@ -0,0 +1,17 @@
import pytest
import unittest
import requests
from django.test import TestCase
from app.tests.abstract.models import TenancyModel
from core.models.manufacturer import Manufacturer
class ManufacturerModel(
TestCase,
TenancyModel
):
model = Manufacturer

View File

@ -1,21 +1,31 @@
from django.test import TestCase, Client
import pytest
import unittest
import requests
from django.test import TestCase
from app.tests.abstract.models import TenancyModel
from core.models.notes import Notes
@pytest.mark.skip(reason="to be written")
def test_note_new_correct_usercreated():
""" The user who added the note must be added to the note """
pass
class NotesModel(
TestCase,
TenancyModel
):
model = Notes
@pytest.mark.skip(reason="to be written")
def test_note_new_correct_usermodified():
""" The user who edited the note must be added to the note """
pass
@pytest.mark.skip(reason="to be written")
def test_note_new_correct_usercreated():
""" The user who added the note must be added to the note """
pass
@pytest.mark.skip(reason="to be written")
def test_note_new_correct_usermodified():
""" The user who edited the note must be added to the note """
pass

View File

@ -4,6 +4,9 @@ from access.mixin import OrganizationPermission
from core.exceptions import MissingAttribute
from settings.models.user_settings import UserSettings
class View(OrganizationPermission):
""" Abstract class common to all views
@ -36,6 +39,11 @@ class AddView(View, generic.CreateView):
template_name:str = 'form.html.j2'
def get_initial(self):
return {
'organization': UserSettings.objects.get(user = self.request.user).default_organization
}
class ChangeView(View, generic.UpdateView):

View File

@ -4,9 +4,15 @@ from core.forms.common import CommonModelForm
from itam.models.device_models import DeviceModel
from settings.forms.admin_settings_global import AdminGlobalModels
class DeviceModelForm(CommonModelForm):
class DeviceModelForm(
AdminGlobalModels,
CommonModelForm
):
class Meta:

View File

@ -4,9 +4,15 @@ from core.forms.common import CommonModelForm
from itam.models.device import DeviceType
from settings.forms.admin_settings_global import AdminGlobalModels
class DeviceTypeForm(CommonModelForm):
class DeviceTypeForm(
AdminGlobalModels,
CommonModelForm
):
class Meta:

View File

@ -4,9 +4,14 @@ from core.forms.common import CommonModelForm
from itam.models.software import SoftwareCategory
from settings.forms.admin_settings_global import AdminGlobalModels
class SoftwareCategoryForm(CommonModelForm):
class SoftwareCategoryForm(
AdminGlobalModels,
CommonModelForm
):
class Meta:

View File

@ -1,6 +1,7 @@
# Generated by Django 5.0.6 on 2024-05-15 06:10
# Generated by Django 5.0.7 on 2024-07-12 03:55
import access.fields
import access.models
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
@ -12,19 +13,38 @@ class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='DeviceType',
name='DeviceModel',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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, unique=True)),
('slug', access.fields.AutoSlugField()),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('manufacturer', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.manufacturer')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
],
options={
'ordering': ['manufacturer', 'name'],
},
),
migrations.CreateModel(
name='DeviceType',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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, unique=True)),
('slug', access.fields.AutoSlugField()),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
],
options={
'abstract': False,
@ -34,46 +54,69 @@ class Migration(migrations.Migration):
name='Device',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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, unique=True)),
('slug', access.fields.AutoSlugField()),
('serial_number', models.CharField(blank=True, default=None, max_length=50, null=True, verbose_name='Serial Number')),
('uuid', models.CharField(blank=True, default=None, max_length=50, null=True, verbose_name='UUID')),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('device_type', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicetype')),
('serial_number', models.CharField(blank=True, default=None, help_text='Serial number of the device.', max_length=50, null=True, unique=True, verbose_name='Serial Number')),
('uuid', models.CharField(blank=True, default=None, help_text='System GUID/UUID.', max_length=50, null=True, unique=True, verbose_name='UUID')),
('inventorydate', models.DateTimeField(blank=True, null=True, verbose_name='Last Inventory Date')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('device_model', models.ForeignKey(blank=True, default=None, help_text='Model of the device.', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicemodel')),
('device_type', models.ForeignKey(blank=True, default=None, help_text='Type of device.', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicetype')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Software',
name='OperatingSystem',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50, unique=True)),
('slug', access.fields.AutoSlugField()),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('name', models.CharField(max_length=50, unique=True)),
('slug', access.fields.AutoSlugField()),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('publisher', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.manufacturer')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='DeviceSoftware',
name='OperatingSystemVersion',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
('action', models.CharField(choices=[('1', 'Install'), ('0', 'Remove')], default=None, max_length=1)),
('name', models.CharField(max_length=50, verbose_name='Major Version')),
('operating_system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='DeviceOperatingSystem',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('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)),
('version', models.CharField(max_length=15, verbose_name='Installed Version')),
('installdate', models.DateTimeField(blank=True, default=None, null=True, verbose_name='Install Date')),
('device', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.device')),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('software', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.software')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('operating_system_version', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystemversion', verbose_name='Operating System/Version')),
],
options={
'abstract': False,
@ -83,36 +126,71 @@ class Migration(migrations.Migration):
name='SoftwareCategory',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50, unique=True)),
('slug', access.fields.AutoSlugField()),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='software',
name='category',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwarecategory'),
),
migrations.CreateModel(
name='SoftwareVersion',
name='Software',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('name', models.CharField(max_length=50, unique=True)),
('slug', access.fields.AutoSlugField()),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('publisher', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.manufacturer')),
('category', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwarecategory')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='SoftwareVersion',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('slug', access.fields.AutoSlugField()),
('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)),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('software', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='itam.software')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='DeviceSoftware',
fields=[
('is_global', models.BooleanField(default=False)),
('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')),
('id', models.AutoField(primary_key=True, serialize=False, unique=True)),
('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)),
('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)),
('action', models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, max_length=1, null=True)),
('installed', models.DateTimeField(blank=True, null=True, verbose_name='Install Date')),
('device', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.device')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
('software', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.software')),
('installedversion', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='installedversion', to='itam.softwareversion')),
('version', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion')),
],
options={
'ordering': ['-action', 'software'],
},
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-17 10:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='softwareversion',
name='name',
field=models.CharField(max_length=50),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-17 10:18
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0002_alter_softwareversion_name'),
]
operations = [
migrations.AddField(
model_name='devicesoftware',
name='version',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.softwareversion'),
),
]

View File

@ -1,47 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-18 08:51
import access.fields
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
('itam', '0003_devicesoftware_version'),
]
operations = [
migrations.CreateModel(
name='OperatingSystem',
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, unique=True)),
('slug', access.fields.AutoSlugField()),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='OperatingSystemVersion',
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)),
('operating_system', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystem')),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,38 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-18 15:20
import access.fields
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0001_initial'),
('itam', '0004_operatingsystem_operatingsystemversion'),
]
operations = [
migrations.AlterField(
model_name='operatingsystemversion',
name='name',
field=models.CharField(max_length=50, verbose_name='Major Version'),
),
migrations.CreateModel(
name='DeviceOperatingSystem',
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)),
('version', models.CharField(max_length=15, verbose_name='Installed Version')),
('device', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.device')),
('operating_system_version', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='itam.operatingsystemversion', verbose_name='Operating System/Version')),
('organization', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,38 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-20 06:36
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0005_alter_operatingsystemversion_name_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='devicesoftware',
options={'ordering': ['-action', 'software']},
),
migrations.AddField(
model_name='deviceoperatingsystem',
name='installdate',
field=models.DateTimeField(blank=True, default=None, null=True, verbose_name='Install Date'),
),
migrations.AddField(
model_name='devicesoftware',
name='installed',
field=models.DateTimeField(blank=True, null=True, verbose_name='Install Date'),
),
migrations.AddField(
model_name='devicesoftware',
name='installedversion',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='installedversion', to='itam.softwareversion'),
),
migrations.AlterField(
model_name='devicesoftware',
name='action',
field=models.CharField(blank=True, choices=[('1', 'Install'), ('0', 'Remove')], default=None, max_length=1, null=True),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-20 09:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0006_alter_devicesoftware_options_and_more'),
]
operations = [
migrations.AddField(
model_name='device',
name='inventorydate',
field=models.DateTimeField(blank=True, null=True, verbose_name='Last Inventory Date'),
),
]

View File

@ -1,60 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 10:37
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0002_alter_team_organization'),
('itam', '0007_device_inventorydate'),
]
operations = [
migrations.AlterField(
model_name='device',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='deviceoperatingsystem',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='devicesoftware',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='devicetype',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='operatingsystem',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='operatingsystemversion',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='software',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='softwarecategory',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
migrations.AlterField(
model_name='softwareversion',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization'),
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 12:05
import access.fields
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'),
('core', '0005_manufacturer'),
('itam', '0008_alter_device_organization_and_more'),
]
operations = [
migrations.CreateModel(
name='DeviceModel',
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, unique=True)),
('slug', access.fields.AutoSlugField()),
('manufacturer', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.manufacturer')),
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization')),
],
options={
'ordering': ['manufacturer', 'name'],
},
),
migrations.AddField(
model_name='device',
name='device_model',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicemodel'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 12:48
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_manufacturer'),
('itam', '0009_devicemodel_device_device_model'),
]
operations = [
migrations.AddField(
model_name='operatingsystem',
name='publisher',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.manufacturer'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-23 12:49
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_manufacturer'),
('itam', '0010_operatingsystem_publisher_software_publisher'),
]
operations = [
migrations.AddField(
model_name='software',
name='publisher',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.manufacturer'),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-28 06:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0011_software_publisher'),
]
operations = [
migrations.AlterField(
model_name='device',
name='serial_number',
field=models.CharField(blank=True, default=None, max_length=50, null=True, unique=True, verbose_name='Serial Number'),
),
migrations.AlterField(
model_name='device',
name='uuid',
field=models.CharField(blank=True, default=None, max_length=50, null=True, unique=True, verbose_name='UUID'),
),
]

View File

@ -1,66 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-05 09:16
import access.models
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0003_alter_team_organization'),
('itam', '0012_alter_device_serial_number_alter_device_uuid'),
]
operations = [
migrations.AlterField(
model_name='device',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='devicemodel',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='deviceoperatingsystem',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='devicesoftware',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='devicetype',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='operatingsystem',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='operatingsystemversion',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='software',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='softwarecategory',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
migrations.AlterField(
model_name='softwareversion',
name='organization',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]),
),
]

View File

@ -1,63 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-11 20:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0013_alter_device_organization_and_more'),
]
operations = [
migrations.AddField(
model_name='device',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='devicemodel',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='deviceoperatingsystem',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='devicesoftware',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='devicetype',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='operatingsystem',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='operatingsystemversion',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='software',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='softwarecategory',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
migrations.AddField(
model_name='softwareversion',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True),
),
]

View File

@ -1,34 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-17 08:56
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0014_device_model_notes_devicemodel_model_notes_and_more'),
]
operations = [
migrations.AlterField(
model_name='device',
name='device_model',
field=models.ForeignKey(blank=True, default=None, help_text='Model of the device.', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicemodel'),
),
migrations.AlterField(
model_name='device',
name='device_type',
field=models.ForeignKey(blank=True, default=None, help_text='Type of device.', null=True, on_delete=django.db.models.deletion.CASCADE, to='itam.devicetype'),
),
migrations.AlterField(
model_name='device',
name='serial_number',
field=models.CharField(blank=True, default=None, help_text='Serial number of the device.', max_length=50, null=True, unique=True, verbose_name='Serial Number'),
),
migrations.AlterField(
model_name='device',
name='uuid',
field=models.CharField(blank=True, default=None, help_text='System GUID/UUID.', max_length=50, null=True, unique=True, verbose_name='UUID'),
),
]

View File

@ -1,63 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-11 04:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('itam', '0015_alter_device_device_model_alter_device_device_type_and_more'),
]
operations = [
migrations.AlterField(
model_name='device',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='devicemodel',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='deviceoperatingsystem',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='devicesoftware',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='devicetype',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='operatingsystem',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='operatingsystemversion',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='software',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='softwarecategory',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
migrations.AlterField(
model_name='softwareversion',
name='model_notes',
field=models.TextField(blank=True, default=None, null=True, verbose_name='Notes'),
),
]

View File

@ -76,7 +76,6 @@ class Device(DeviceCommonFieldsName, SaveHistory):
null = True,
blank= True,
help_text = 'Type of device.',
)
@ -86,6 +85,43 @@ class Device(DeviceCommonFieldsName, SaveHistory):
blank = True,
)
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
""" Save Device Model
After saving the device update the related items so that they are a part
of the same organization as the device.
"""
super().save(
force_insert=False, force_update=False, using=None, update_fields=None
)
models_to_update =[
DeviceSoftware,
DeviceOperatingSystem
]
for update_model in models_to_update:
obj = update_model.objects.filter(
device = self.id,
)
if obj.exists():
obj.update(
is_global = False,
organization = self.organization,
)
from config_management.models.groups import ConfigGroupHosts
ConfigGroupHosts.objects.filter(
host = self.id,
).delete()
def __str__(self):
@ -258,6 +294,16 @@ class DeviceSoftware(DeviceCommonFields, SaveHistory):
return self.device
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
self.is_global = False
super().save(
force_insert=False, force_update=False, using=None, update_fields=None
)
class DeviceOperatingSystem(DeviceCommonFields, SaveHistory):
@ -300,3 +346,14 @@ class DeviceOperatingSystem(DeviceCommonFields, SaveHistory):
""" Fetch the parent object """
return self.device
def save(
self, force_insert=False, force_update=False, using=None, update_fields=None
):
self.is_global = False
super().save(
force_insert=False, force_update=False, using=None, update_fields=None
)

View File

@ -6,16 +6,14 @@ import pytest
import unittest
import requests
# from django.contrib.auth import get_user_model
# from django.core.exceptions import ValidationError
# from access.models import Organization
# from access.models import Organization
from app.tests.abstract.models import TenancyModel
from itam.models.device import Device
class Device(
TestCase
TestCase,
TenancyModel,
):
model = Device
@ -40,6 +38,16 @@ class Device(
# name = 'deviceone'
# )
@pytest.mark.skip(reason="to be written")
def test_device_move_organization(user):
"""Move Organization test
When a device moves organization, devicesoftware and devicesoftware table data
must also move organizations
"""
pass
@pytest.mark.skip(reason="to be written")
def test_device_software_action(user):
"""Ensure only software that is from the same organization or is global can be added to the device

View File

@ -1,18 +0,0 @@
import pytest
import unittest
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.device import Device
class DeviceTenancyObject(
TestCase,
TenancyObject
):
model = Device

View File

@ -1,44 +1,46 @@
# from django.conf import settings
# from django.shortcuts import reverse
from django.test import TestCase, Client
import pytest
import unittest
import requests
# from django.contrib.auth import get_user_model
# from django.core.exceptions import ValidationError
# from access.models import Organization
from django.test import TestCase, Client
# class Test_app_structure_auth(unittest.TestCase):
# User = get_user_model()
from app.tests.abstract.models import TenancyModel
from itam.models.device_models import DeviceModel
@pytest.mark.skip(reason="to be written")
def test_device_model_software_action(user):
"""Ensure only software that is from the same organization or is global can be added to the device
"""
pass
class DeviceModelModel(
TestCase,
TenancyModel
):
model = DeviceModel
@pytest.mark.skip(reason="to be written")
def test_device_model_software_action(user):
"""Ensure only software that is from the same organization or is global can be added to the device
"""
pass
@pytest.mark.skip(reason="to be written")
def test_device_model_must_have_organization(user):
""" Device Model must have organization set """
pass
@pytest.mark.skip(reason="to be written")
def test_device_model_must_have_organization(user):
""" Device Model must have organization set """
pass
@pytest.mark.skip(reason="to be written")
def test_device_model_not_global(user):
"""Devices are not global items.
@pytest.mark.skip(reason="to be written")
def test_device_model_not_global(user):
"""Devices are not global items.
Ensure that a device can't be set to be global.
"""
pass
Ensure that a device can't be set to be global.
"""
pass
@pytest.mark.skip(reason="to be written")
def test_device_model_operating_system_version_only_one(user):
"""model deviceoperatingsystem must only contain one value per device
"""
pass
@pytest.mark.skip(reason="to be written")
def test_device_model_operating_system_version_only_one(user):
"""model deviceoperatingsystem must only contain one value per device
"""
pass

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.device import DeviceModel
class DeviceModelTenancyObject(
TestCase,
TenancyObject
):
model = DeviceModel

View File

@ -5,6 +5,8 @@ from django.test import TestCase
from access.models import Organization
from app.tests.abstract.models import TenancyModel
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
from itam.models.device import Device, DeviceOperatingSystem
@ -12,7 +14,10 @@ from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
class DeviceOperatingSystemModel(TestCase):
class DeviceOperatingSystemModel(
TestCase,
TenancyModel,
):
model = DeviceOperatingSystem

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.device import DeviceOperatingSystem
class DeviceOperatingSystemTenancyObject(
TestCase,
TenancyObject
):
model = DeviceOperatingSystem

View File

@ -5,6 +5,8 @@ from django.test import TestCase
from access.models import Organization
from app.tests.abstract.models import TenancyModel
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
from itam.models.device import Device, DeviceSoftware
@ -12,12 +14,14 @@ from itam.models.software import Software
class DeviceSoftwareModel(TestCase):
class DeviceSoftwareModel(
TestCase,
TenancyModel,
):
model = DeviceSoftware
@classmethod
def setUpTestData(self):
""" Setup Test

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.device import DeviceSoftware
class DeviceSoftwareTenancyObject(
TestCase,
TenancyObject
):
model = DeviceSoftware

View File

@ -0,0 +1,17 @@
import pytest
import unittest
import requests
from django.test import TestCase
from app.tests.abstract.models import TenancyModel
from itam.models.device import DeviceType
class DeviceTypeModel(
TestCase,
TenancyModel
):
model = DeviceType

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.device import DeviceType
class DeviceTypeTenancyObject(
TestCase,
TenancyObject
):
model = DeviceType

View File

@ -6,42 +6,50 @@ import pytest
import unittest
import requests
# from django.contrib.auth import get_user_model
# from django.core.exceptions import ValidationError
# from access.models import Organization
# class Test_app_structure_auth(unittest.TestCase):
from app.tests.abstract.models import TenancyModel
from itam.models.operating_system import OperatingSystem
@pytest.mark.skip(reason="to be written")
def test_operating_system_must_have_organization(user):
""" Operating_system must have organization set """
pass
@pytest.mark.skip(reason="to be written")
def test_operating_system_update_is_global_no_change(user):
"""Once operating_system is set to global it can't be changed.
class OperatingSystemModel(
TestCase,
TenancyModel
):
global status can't be changed as non-global items may reference the item.
"""
pass
@pytest.mark.skip(reason="to be written")
def test_operating_system_prevent_delete_if_used(user):
"""Any operating_system in use by a operating_system must not be deleted.
i.e. A global os can't be deleted
"""
pass
model = OperatingSystem
@pytest.mark.skip(reason="to be written")
def test_operating_system_version_installs_by_os_count(user):
"""Operating System Versions has a count field that must be accurate
@pytest.mark.skip(reason="to be written")
def test_operating_system_must_have_organization(user):
""" Operating_system must have organization set """
pass
The count is of model OperatingSystemVersion linked to model operating_systemOperatingSystem
"""
@pytest.mark.skip(reason="to be written")
def test_operating_system_update_is_global_no_change(user):
"""Once operating_system is set to global it can't be changed.
pass
global status can't be changed as non-global items may reference the item.
"""
pass
@pytest.mark.skip(reason="to be written")
def test_operating_system_prevent_delete_if_used(user):
"""Any operating_system in use by a operating_system must not be deleted.
i.e. A global os can't be deleted
"""
pass
@pytest.mark.skip(reason="to be written")
def test_operating_system_version_installs_by_os_count(user):
"""Operating System Versions has a count field that must be accurate
The count is of model OperatingSystemVersion linked to model operating_systemOperatingSystem
"""
pass

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.operating_system import OperatingSystem
class OperatingSystemTenancyObject(
TestCase,
TenancyObject
):
model = OperatingSystem

View File

@ -5,13 +5,18 @@ from django.test import TestCase
from access.models import Organization
from app.tests.abstract.models import TenancyModel
from config_management.models.groups import ConfigGroups, ConfigGroupSoftware
from itam.models.operating_system import OperatingSystem, OperatingSystemVersion
class OperatingSystemVersionModel(TestCase):
class OperatingSystemVersionModel(
TestCase,
TenancyModel,
):
model = OperatingSystemVersion

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.operating_system import OperatingSystemVersion
class OperatingSystemVersionTenancyObject(
TestCase,
TenancyObject
):
model = OperatingSystemVersion

View File

@ -1,37 +1,42 @@
# from django.conf import settings
# from django.shortcuts import reverse
from django.test import TestCase, Client
import pytest
import unittest
import requests
# from django.contrib.auth import get_user_model
# from django.core.exceptions import ValidationError
# from access.models import Organization
from django.test import TestCase, Client
# class Test_app_structure_auth(unittest.TestCase):
from app.tests.abstract.models import TenancyModel
from itam.models.software import Software
@pytest.mark.skip(reason="to be written")
def test_software_must_have_organization(user):
""" Software must have organization set """
pass
@pytest.mark.skip(reason="to be written")
def test_software_update_is_global_no_change(user):
"""Once software is set to global it can't be changed.
class SoftwareModel(
TestCase,
TenancyModel
):
global status can't be changed as non-global items may reference the item.
"""
model = Software
pass
@pytest.mark.skip(reason="to be written")
def test_software_prevent_delete_if_used(user):
"""Any software in use by a software must not be deleted.
@pytest.mark.skip(reason="to be written")
def test_software_must_have_organization(self):
""" Software must have organization set """
pass
i.e. A software has an action set for the software.
"""
@pytest.mark.skip(reason="to be written")
def test_software_update_is_global_no_change(self):
"""Once software is set to global it can't be changed.
pass
global status can't be changed as non-global items may reference the item.
"""
pass
@pytest.mark.skip(reason="to be written")
def test_software_prevent_delete_if_used(self):
"""Any software in use by a software must not be deleted.
i.e. A software has an action set for the software.
"""
pass

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.software import Software
class SoftwareTenancyObject(
TestCase,
TenancyObject
):
model = Software

View File

@ -0,0 +1,18 @@
import pytest
import unittest
import requests
from django.test import TestCase
from app.tests.abstract.models import TenancyModel
from itam.models.software import SoftwareCategory
class SoftwareCategoryModel(
TestCase,
TenancyModel
):
model = SoftwareCategory

View File

@ -1,18 +0,0 @@
import pytest
import unittest
import requests
from django.test import TestCase, Client
from access.tests.abstract.tenancy_object import TenancyObject
from itam.models.software import SoftwareCategory
class SoftwareCategoryTenancyObject(
TestCase,
TenancyObject
):
model = SoftwareCategory

View File

@ -3,7 +3,6 @@ import markdown
from django.contrib.auth import decorators as auth_decorator
from django.core.paginator import Paginator
from django.db.models import Q
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.decorators import method_decorator
@ -61,7 +60,7 @@ class IndexView(IndexView):
else:
return Device.objects.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True)).order_by('name')
return Device.objects.filter().order_by('name')

View File

@ -7,7 +7,6 @@ from core.views.common import AddView, ChangeView, DeleteView, IndexView
from itam.models.device import DeviceType
from itam.forms.device_type import DeviceTypeForm
from settings.models.user_settings import UserSettings
@ -61,13 +60,6 @@ class Add(AddView):
template_name = 'form.html.j2'
def get_initial(self):
return {
'organization': UserSettings.objects.get(user = self.request.user).default_organization
}
def get_success_url(self, **kwargs):
return reverse('Settings:_device_types')

View File

@ -1,5 +1,5 @@
from django.contrib.auth import decorators as auth_decorator
from django.db.models import Q, Count
from django.db.models import Count
from django.urls import reverse
from django.utils.decorators import method_decorator
@ -40,7 +40,7 @@ class IndexView(IndexView):
else:
return OperatingSystem.objects.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True)).order_by('name')
return OperatingSystem.objects.filter().order_by('name')

View File

@ -1,5 +1,5 @@
from django.contrib.auth import decorators as auth_decorator
from django.db.models import Count, Q
from django.db.models import Count
from django.urls import reverse
from django.utils.decorators import method_decorator
@ -47,7 +47,7 @@ class IndexView(IndexView):
else:
return Software.objects.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True)).order_by('name')
return Software.objects.filter().order_by('name')
@ -98,9 +98,9 @@ class View(ChangeView):
)
elif not self.request.user.is_superuser:
context['device_software'] = DeviceSoftware.objects.filter(
Q(device__in=self.user_organizations(),
software=self.kwargs['pk'])
software=self.kwargs['pk']
).order_by(
'device',
'organization'

View File

View File

@ -0,0 +1,31 @@
from django import forms
from django.db.models import Q
from django.contrib.auth.models import User
from access.models import Organization, TeamUsers
from core.forms.common import CommonModelForm
from settings.models.app_settings import AppSettings
class AdminGlobalModels:
"""Administratively set Global Models
Use this class on models that can be set within the application settings as a global
application.
"""
def __init__(self, *args, **kwargs):
""" Init Form
As these forms are for administratively set global organization, set the `organization` and `is_global` fields
to be read only.
"""
super().__init__(*args, **kwargs)
self.fields['organization'].widget.attrs['readonly'] = True
self.fields['is_global'].widget.attrs['readonly'] = True

View File

@ -1,23 +1,84 @@
# Generated by Django 5.0.6 on 2024-05-23 10:13
# Generated by Django 5.0.7 on 2024-07-12 03:55
import access.fields
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
from django.contrib.auth.models import User
from settings.models.user_settings import UserSettings
def add_user_settings(apps, schema_editor):
for user in User.objects.all():
if not UserSettings.objects.filter(pk=user.id).exists():
user_setting = UserSettings.objects.create(
user=user
)
user_setting.save()
def add_app_settings(apps, schema_editor):
app = apps.get_model('settings', 'appsettings')
if not app.objects.filter(owner_organization=None).exists():
setting = app.objects.create(
owner_organization=None
)
setting.save()
class Migration(migrations.Migration):
initial = True
dependencies = [
('access', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Settings',
name='AppSettings',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('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)),
('device_model_is_global', models.BooleanField(default=False, verbose_name='All Device Models are global')),
('device_type_is_global', models.BooleanField(default=False, verbose_name='All Device Types is global')),
('manufacturer_is_global', models.BooleanField(default=False, verbose_name='All Manufacturer / Publishers are global')),
('software_is_global', models.BooleanField(default=False, verbose_name='All Software is global')),
('software_categories_is_global', models.BooleanField(default=False, verbose_name='All Software Categories are global')),
('global_organization', models.ForeignKey(blank=True, default=None, help_text='Organization global items will be created in', null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='global_organization', to='access.organization')),
('owner_organization', models.ForeignKey(blank=True, default=None, help_text='Organization the settings belong to', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owner_organization', to='access.organization')),
],
options={
'managed': False,
'abstract': False,
},
),
migrations.CreateModel(
name='UserSettings',
fields=[
('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)),
('default_organization', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='access.organization')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
migrations.RunPython(add_user_settings),
migrations.RunPython(add_app_settings),
]

View File

@ -1,32 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-24 23:50
import access.fields
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('access', '0002_alter_team_organization'),
('settings', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserSettings',
fields=[
('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)),
('default_organization', models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='access.organization')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'abstract': False,
},
),
]

View File

@ -1,37 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-24 23:19
import access.fields
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
from django.contrib.auth.models import User
from settings.models.user_settings import UserSettings
def add_user_settings(apps, schema_editor):
for user in User.objects.all():
if not UserSettings.objects.filter(pk=user.id).exists():
user_setting = UserSettings.objects.create(
user=user
)
user_setting.save()
class Migration(migrations.Migration):
dependencies = [
('access', '0002_alter_team_organization'),
('settings', '0002_usersettings'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RunPython(add_user_settings),
]

View File

@ -1,31 +0,0 @@
# Generated by Django 5.0.6 on 2024-05-25 02:42
import access.fields
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'),
('settings', '0003_create_settings_for_all_users'),
]
operations = [
migrations.CreateModel(
name='AppSettings',
fields=[
('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)),
('software_is_global', models.BooleanField(default=False, verbose_name='All Software is global')),
('global_organization', models.ForeignKey(blank=True, default=None, help_text='Organization global items will be created in', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='global_organization', to='access.organization')),
('owner_organization', models.ForeignKey(blank=True, default=None, help_text='Organization the settings belong to', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owner_organization', to='access.organization')),
],
options={
'abstract': False,
},
),
]

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