Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
c496d10c1a | |||
3993cc96a5 | |||
a4b37b34a9 | |||
2f55024f0b | |||
213644a51a | |||
281d839801 | |||
4fd157a785 | |||
968b3a0f92 | |||
f5ba608ed1 | |||
289668bb7f | |||
9e28722dba | |||
9b673f4a07 | |||
3a9e4b29b3 | |||
8d59462561 | |||
098e41e6a1 | |||
fc3f0b39e2 | |||
de53948cea | |||
823ebc0eb5 | |||
41414438d1 | |||
5704560beb | |||
8a48902b64 | |||
61fe059513 | |||
94576cc733 | |||
3a32c62119 | |||
9ea4fe1adc | |||
0798a672c2 | |||
f4e68529ba | |||
92a411baec | |||
034857d088 | |||
e5ce86a9bb | |||
5188b3d52e | |||
61b9435d1f | |||
8244676530 | |||
ec1e7cca85 | |||
72ab9253d7 | |||
4f89255c4f | |||
8d6d1d0d56 | |||
2d0c3a660a | |||
974a208869 | |||
7f225784c2 | |||
a3be95013c | |||
adefbf3960 | |||
9a1ca7a104 | |||
e84e80cd8f | |||
ebc266010a | |||
519277e18b | |||
a5a5874211 | |||
fa2b90ee7b | |||
5c74360842 | |||
8457f15eca | |||
5bc5a4b065 | |||
40350d166e | |||
9a94ba31e4 | |||
55197e7dcc | |||
a67bc70503 | |||
60538e1cec | |||
416e029c23 | |||
fe64c11927 | |||
1f8244ae40 | |||
9871cf248b | |||
a1759ecaaf | |||
30e0342f52 | |||
5a201ef548 | |||
7b26fac73d | |||
7c62309a31 | |||
621cbd2d71 | |||
d8e89bee10 | |||
4ee62aa399 | |||
f1201e8933 | |||
9acc4fdfcb | |||
6776612b66 | |||
af3e770760 | |||
fbe7e63cc9 | |||
aec460306b |
3
.cz.yaml
3
.cz.yaml
@ -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-b5
|
||||
version_scheme: semver
|
||||
|
126
.gitlab-ci.yml
126
.gitlab-ci.yml
@ -15,6 +15,9 @@ variables:
|
||||
DOCKER_IMAGE_PUBLISH_REGISTRY: docker.io/nofusscomputing
|
||||
DOCKER_IMAGE_PUBLISH_URL: https://hub.docker.com/r/nofusscomputing/$DOCKER_IMAGE_PUBLISH_NAME
|
||||
|
||||
# Extra release commands
|
||||
MY_COMMAND: ./.gitlab/additional_actions_bump.sh
|
||||
|
||||
# Docs NFC
|
||||
PAGES_ENVIRONMENT_PATH: projects/centurion_erp/
|
||||
|
||||
@ -115,6 +118,120 @@ 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 curl
|
||||
- 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:
|
||||
- if [ "$CI_COMMIT_BRANCH" == "development" ] ; then RELEASE_CHANGELOG=$(cz bump --changelog --changelog-to-stdout --prerelease beta); else RELEASE_CHANGELOG=$(cz bump --changelog --changelog-to-stdout); fi
|
||||
- RELEASE_VERSION_NEW=$(cz version --project)
|
||||
- export 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] not running extra actions, no new version";
|
||||
|
||||
else
|
||||
|
||||
echo "[DEBUG] Creating new Version Label";
|
||||
|
||||
echo "----------------------------";
|
||||
|
||||
echo ${MY_COMMAND};
|
||||
|
||||
echo "----------------------------";
|
||||
|
||||
cat ${MY_COMMAND};
|
||||
|
||||
echo "----------------------------";
|
||||
|
||||
${MY_COMMAND};
|
||||
|
||||
echo "----------------------------";
|
||||
fi
|
||||
|
||||
- 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 +279,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
|
||||
|
||||
|
7
.gitlab/additional_actions_bump.sh
Executable file
7
.gitlab/additional_actions_bump.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Create Version label wtihn repo
|
||||
curl \
|
||||
--data "name=v${RELEASE_TAG}&color=#eee600&description=Version%20that%20is%20affected" \
|
||||
--header "PRIVATE-TOKEN: $GIT_COMMIT_TOKEN" \
|
||||
"https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/labels"
|
@ -20,16 +20,18 @@
|
||||
|
||||
<!-- 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/)._
|
||||
|
||||
- [ ] Release notes updated
|
||||
|
||||
- [ ] ~Documentation Documentation written
|
||||
|
||||
_All features to be documented within the correct section(s). Administration, Development and/or User_
|
||||
|
||||
- [ ] Milestone assigned
|
||||
|
||||
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/django-template/development/testing/)
|
||||
- [ ] [Unit Test(s) Written](https://nofusscomputing.com/projects/centurion_erp/development/testing/)
|
||||
|
||||
_ensure test coverage delta is not less than zero_
|
||||
|
103
CHANGELOG.md
103
CHANGELOG.md
@ -1,3 +1,106 @@
|
||||
## 1.0.0-b5 (2024-07-31)
|
||||
|
||||
### Feat
|
||||
|
||||
- **api**: Add device config groups to devices
|
||||
- **api**: Ability to fetch configgroups from api along with config
|
||||
|
||||
### Fix
|
||||
|
||||
- **api**: Ensure device groups is read only
|
||||
|
||||
## 1.0.0-b4 (2024-07-29)
|
||||
|
||||
### Feat
|
||||
|
||||
- **swagger**: remove `{format}` suffixed doc entries
|
||||
|
||||
### Fix
|
||||
|
||||
- **api**: cleanup team post/get
|
||||
- **api**: confirm HTTP method is allowed before permission check
|
||||
- **api**: Ensure that organizations can't be created via the API
|
||||
- **access**: Team model class inheritance order corrected
|
||||
|
||||
## 1.0.0-b3 (2024-07-21)
|
||||
|
||||
### Fix
|
||||
|
||||
- **itam**: Limit os version count to devices user has access to
|
||||
|
||||
## 1.0.0-b2 (2024-07-19)
|
||||
|
||||
### Fix
|
||||
|
||||
- **itam**: only show os version once
|
||||
|
||||
## 1.0.0-b1 (2024-07-19)
|
||||
|
||||
### Fix
|
||||
|
||||
- **itam**: ensure installed operating system count is limited to users organizations
|
||||
- **itam**: ensure installed software count is limited to users organizations
|
||||
|
||||
## 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
|
||||
|
7
Release-Notes.md
Normal file
7
Release-Notes.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Version 1.0.0
|
||||
|
||||
Initial Release of Centurion ERP.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
- Nil
|
@ -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',
|
||||
]
|
||||
|
@ -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',
|
||||
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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]),
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -80,7 +80,9 @@ class OrganizationMixin():
|
||||
|
||||
id = int(self.request.POST.get(field))
|
||||
|
||||
|
||||
except:
|
||||
|
||||
pass
|
||||
|
||||
|
||||
return id
|
||||
|
@ -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,7 +189,8 @@ class TenancyObject(models.Model):
|
||||
return self.organization
|
||||
|
||||
|
||||
class Team(Group, TenancyObject, SaveHistory):
|
||||
|
||||
class Team(Group, TenancyObject):
|
||||
class Meta:
|
||||
# proxy = True
|
||||
verbose_name_plural = "Teams"
|
||||
|
@ -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
|
||||
|
@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
@ -24,7 +24,7 @@ class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionVie
|
||||
|
||||
url_name = '_api_organization'
|
||||
|
||||
url_list = 'device-list'
|
||||
url_list = '_api_orgs'
|
||||
|
||||
change_data = {'name': 'device'}
|
||||
|
||||
@ -124,6 +124,8 @@ class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionVie
|
||||
delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
self.super_user = User.objects.create_user(username="super_user", password="password", is_superuser=True)
|
||||
|
||||
self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
@ -171,3 +173,67 @@ class OrganizationPermissionsAPI(TestCase, APIPermissionChange, APIPermissionVie
|
||||
team = different_organization_team,
|
||||
user = self.different_organization_user
|
||||
)
|
||||
|
||||
|
||||
def test_add_is_prohibited_anon_user(self):
|
||||
""" Ensure Organization cant be created
|
||||
|
||||
Attempt to create organization as anon user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_list)
|
||||
|
||||
|
||||
# client.force_login(self.add_user)
|
||||
response = client.post(url, data={'name': 'should not create'}, content_type='application/json')
|
||||
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
def test_add_is_prohibited_diff_org_user(self):
|
||||
""" Ensure Organization cant be created
|
||||
|
||||
Attempt to create organization as user with different org permissions.
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_list)
|
||||
|
||||
|
||||
client.force_login(self.different_organization_user)
|
||||
response = client.post(url, data={'name': 'should not create'}, content_type='application/json')
|
||||
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
def test_add_is_prohibited_super_user(self):
|
||||
""" Ensure Organization cant be created
|
||||
|
||||
Attempt to create organization as user who is super user
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_list)
|
||||
|
||||
|
||||
client.force_login(self.super_user)
|
||||
response = client.post(url, data={'name': 'should not create'}, content_type='application/json')
|
||||
|
||||
assert response.status_code == 405
|
||||
|
||||
|
||||
def test_add_is_prohibited_user_same_org(self):
|
||||
""" Ensure Organization cant be created
|
||||
|
||||
Attempt to create organization as user with permission
|
||||
"""
|
||||
|
||||
client = Client()
|
||||
url = reverse(self.app_namespace + ':' + self.url_list)
|
||||
|
||||
|
||||
client.force_login(self.add_user)
|
||||
response = client.post(url, data={'name': 'should not create'}, content_type='application/json')
|
||||
|
||||
assert response.status_code == 405
|
||||
|
@ -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,13 @@ 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
|
||||
|
||||
@pytest.mark.skip(reason="uses Django group manager")
|
||||
def test_model_class_tenancy_manager_function_get_queryset_called(self):
|
||||
pass
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -14,9 +14,8 @@ class TeamSerializerBase(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Team
|
||||
fields = (
|
||||
"id",
|
||||
"team_name",
|
||||
'organization',
|
||||
'team_name',
|
||||
'permissions',
|
||||
'url',
|
||||
)
|
||||
|
||||
@ -29,9 +28,18 @@ class TeamSerializerBase(serializers.ModelSerializer):
|
||||
|
||||
|
||||
|
||||
class TeamPermissionSerializer(serializers.ModelSerializer):
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Permission
|
||||
depth = 1
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class TeamSerializer(TeamSerializerBase):
|
||||
|
||||
permissions = serializers.SerializerMethodField('get_url')
|
||||
permissions_url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
def get_url(self, obj):
|
||||
|
||||
@ -63,16 +71,18 @@ class TeamSerializer(TeamSerializerBase):
|
||||
|
||||
class Meta:
|
||||
model = Team
|
||||
depth = 1
|
||||
depth = 2
|
||||
fields = (
|
||||
"id",
|
||||
"team_name",
|
||||
'organization',
|
||||
'permissions',
|
||||
'permissions_url',
|
||||
'url',
|
||||
)
|
||||
read_only_fields = [
|
||||
'permissions',
|
||||
'id',
|
||||
'organization',
|
||||
'permissions_url',
|
||||
'url'
|
||||
]
|
||||
|
||||
@ -111,7 +121,7 @@ class OrganizationSerializer(serializers.ModelSerializer):
|
||||
|
||||
return request.build_absolute_uri(reverse('API:_api_organization_teams', args=[obj.id]))
|
||||
|
||||
teams = TeamSerializerBase(source='team_set', many=True, read_only=False)
|
||||
teams = TeamSerializer(source='team_set', many=True, read_only=False)
|
||||
|
||||
view_name="API:_api_organization"
|
||||
|
||||
|
86
app/api/serializers/config.py
Normal file
86
app/api/serializers/config.py
Normal file
@ -0,0 +1,86 @@
|
||||
from rest_framework import serializers
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
|
||||
class ParentGroupSerializer(serializers.ModelSerializer):
|
||||
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
model = ConfigGroups
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'url',
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def get_url(self, obj):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
|
||||
|
||||
|
||||
|
||||
class ConfigGroupsSerializerBase(serializers.ModelSerializer):
|
||||
|
||||
parent = ParentGroupSerializer(read_only=True)
|
||||
url = serializers.SerializerMethodField('get_url')
|
||||
|
||||
|
||||
class Meta:
|
||||
model = ConfigGroups
|
||||
fields = [
|
||||
'id',
|
||||
'parent',
|
||||
'name',
|
||||
'config',
|
||||
'url',
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'name',
|
||||
'config',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
def get_url(self, obj):
|
||||
|
||||
request = self.context.get('request')
|
||||
|
||||
return request.build_absolute_uri(reverse("API:_api_config_group", args=[obj.pk]))
|
||||
|
||||
|
||||
|
||||
class ConfigGroupsSerializer(ConfigGroupsSerializerBase):
|
||||
|
||||
|
||||
class Meta:
|
||||
model = ConfigGroups
|
||||
depth = 1
|
||||
fields = [
|
||||
'id',
|
||||
'parent',
|
||||
'name',
|
||||
'config',
|
||||
'url',
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'parent',
|
||||
'name',
|
||||
'config',
|
||||
'url',
|
||||
]
|
||||
|
@ -1,9 +1,38 @@
|
||||
from django.urls import reverse
|
||||
|
||||
from itam.models.device import Device
|
||||
from rest_framework import serializers
|
||||
|
||||
from api.serializers.config import ParentGroupSerializer
|
||||
|
||||
from config_management.models.groups import ConfigGroupHosts
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
|
||||
class DeviceConfigGroupsSerializer(serializers.ModelSerializer):
|
||||
|
||||
name = serializers.CharField(source='group.name', read_only=True)
|
||||
|
||||
url = serializers.HyperlinkedIdentityField(
|
||||
view_name="API:_api_config_group", format="html"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
||||
model = ConfigGroupHosts
|
||||
|
||||
fields = [
|
||||
'id',
|
||||
'name',
|
||||
'url',
|
||||
|
||||
]
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'name',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
||||
class DeviceSerializer(serializers.ModelSerializer):
|
||||
@ -13,7 +42,9 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
config = serializers.SerializerMethodField('get_device_config')
|
||||
|
||||
|
||||
groups = DeviceConfigGroupsSerializer(source='configgrouphosts_set', many=True, read_only=True)
|
||||
|
||||
def get_device_config(self, device):
|
||||
|
||||
request = self.context.get('request')
|
||||
@ -22,11 +53,29 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = '__all__'
|
||||
|
||||
read_only_fields = [
|
||||
'inventorydate',
|
||||
depth = 1
|
||||
fields = [
|
||||
'id',
|
||||
'is_global',
|
||||
'slug',
|
||||
'name',
|
||||
'config',
|
||||
'serial_number',
|
||||
'uuid',
|
||||
'inventorydate',
|
||||
'created',
|
||||
'modified',
|
||||
'groups',
|
||||
'organization',
|
||||
'url',
|
||||
]
|
||||
|
||||
read_only_fields = [
|
||||
'id',
|
||||
'config',
|
||||
'inventorydate',
|
||||
'created',
|
||||
'modified',
|
||||
'groups',
|
||||
'url',
|
||||
]
|
||||
|
||||
|
175
app/api/tasks.py
175
app/api/tasks.py
@ -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:
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -3,7 +3,7 @@ from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.urlpatterns import format_suffix_patterns
|
||||
|
||||
from .views import access, index
|
||||
from .views import access, config, index
|
||||
|
||||
from .views.itam import software, config as itam_config
|
||||
from .views.itam.device import DeviceViewSet
|
||||
@ -24,6 +24,9 @@ router.register('software', software.SoftwareViewSet, basename='software')
|
||||
urlpatterns = [
|
||||
path("config/<slug:slug>/", itam_config.View.as_view(), name="_api_device_config"),
|
||||
|
||||
path("configuration/", config.ConfigGroupsList.as_view(), name='_api_config_groups'),
|
||||
path("configuration/<int:pk>", config.ConfigGroupsDetail.as_view(), name='_api_config_group'),
|
||||
|
||||
path("device/inventory", inventory.Collect.as_view(), name="_api_device_inventory"),
|
||||
|
||||
path("organization/", access.OrganizationList.as_view(), name='_api_orgs'),
|
||||
|
@ -1,5 +1,7 @@
|
||||
from django.contrib.auth.models import Permission
|
||||
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
|
||||
|
||||
from rest_framework import generics, routers, serializers, views
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
from rest_framework.response import Response
|
||||
@ -7,12 +9,17 @@ from rest_framework.response import Response
|
||||
from access.mixin import OrganizationMixin
|
||||
from access.models import Organization, Team
|
||||
|
||||
from api.serializers.access import OrganizationSerializer, OrganizationListSerializer, TeamSerializer
|
||||
from api.serializers.access import OrganizationSerializer, OrganizationListSerializer, TeamSerializer, TeamPermissionSerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
|
||||
|
||||
class OrganizationList(generics.ListCreateAPIView):
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch Organizations",
|
||||
description="Returns a list of organizations."
|
||||
),
|
||||
)
|
||||
class OrganizationList(generics.ListAPIView):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
@ -28,7 +35,18 @@ class OrganizationList(generics.ListCreateAPIView):
|
||||
|
||||
|
||||
|
||||
class OrganizationDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Get An Organization",
|
||||
),
|
||||
patch=extend_schema(
|
||||
summary = "Update an organization",
|
||||
),
|
||||
put=extend_schema(
|
||||
summary = "Update an organization",
|
||||
),
|
||||
)
|
||||
class OrganizationDetail(generics.RetrieveUpdateAPIView):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
@ -44,6 +62,20 @@ class OrganizationDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
post=extend_schema(
|
||||
summary = "Create a Team",
|
||||
description = """Create a team within the defined organization.""",
|
||||
tags = ['team',],
|
||||
request = TeamSerializer,
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Team has been updated with the supplied permissions'),
|
||||
401: OpenApiResponse(description='User Not logged in'),
|
||||
403: OpenApiResponse(description='User is missing permission or in different organization'),
|
||||
}
|
||||
),
|
||||
create=extend_schema(exclude=True),
|
||||
)
|
||||
class TeamList(generics.ListCreateAPIView):
|
||||
|
||||
permission_classes = [
|
||||
@ -66,6 +98,45 @@ class TeamList(generics.ListCreateAPIView):
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch a Team",
|
||||
description = """Fetch a team within the defined organization.
|
||||
""",
|
||||
methods=["GET"],
|
||||
tags = ['team',],
|
||||
request = TeamSerializer,
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Team has been updated with the supplied permissions'),
|
||||
401: OpenApiResponse(description='User Not logged in'),
|
||||
403: OpenApiResponse(description='User is missing permission or in different organization'),
|
||||
}
|
||||
),
|
||||
patch=extend_schema(
|
||||
summary = "Update a Team",
|
||||
description = """Update a team within the defined organization.
|
||||
""",
|
||||
methods=["Patch"],
|
||||
tags = ['team',],
|
||||
request = TeamSerializer,
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Team has been updated with the supplied permissions'),
|
||||
401: OpenApiResponse(description='User Not logged in'),
|
||||
403: OpenApiResponse(description='User is missing permission or in different organization'),
|
||||
}
|
||||
),
|
||||
put = extend_schema(
|
||||
summary = "Amend a team",
|
||||
tags = ['team',],
|
||||
),
|
||||
delete=extend_schema(
|
||||
summary = "Delete a Team",
|
||||
tags = ['team',],
|
||||
),
|
||||
post = extend_schema(
|
||||
exclude = True,
|
||||
)
|
||||
)
|
||||
class TeamDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
permission_classes = [
|
||||
@ -79,12 +150,66 @@ class TeamDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
|
||||
|
||||
|
||||
class TeamPermissionDetail(routers.APIRootView):
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch a teams permissions",
|
||||
tags = ['team',],
|
||||
),
|
||||
post=extend_schema(
|
||||
summary = "Replace team Permissions",
|
||||
description = """Replace the teams permissions with the permissions supplied.
|
||||
|
||||
# temp disabled until permission checker updated
|
||||
# permission_classes = [
|
||||
# OrganizationPermissionAPI
|
||||
# ]
|
||||
Teams Permissions will be replaced with the permissions supplied. **ALL** existing permissions will be
|
||||
removed.
|
||||
|
||||
permissions are required to be in format `<module name>_<permission>_<table name>`
|
||||
""",
|
||||
|
||||
methods=["POST"],
|
||||
tags = ['team',],
|
||||
request = TeamPermissionSerializer,
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Team has been updated with the supplied permissions'),
|
||||
401: OpenApiResponse(description='User Not logged in'),
|
||||
403: OpenApiResponse(description='User is missing permission or in different organization'),
|
||||
}
|
||||
),
|
||||
delete=extend_schema(
|
||||
summary = "Delete permissions",
|
||||
tags = ['team',],
|
||||
),
|
||||
patch = extend_schema(
|
||||
summary = "Amend team Permissions",
|
||||
description = """Amend the teams permissions with the permissions supplied.
|
||||
|
||||
Teams permissions will include the existing permissions along with the ones supplied.
|
||||
permissions are required to be in format `<module name>_<permission>_<table name>`
|
||||
""",
|
||||
|
||||
methods=["PATCH"],
|
||||
parameters = None,
|
||||
tags = ['team',],
|
||||
request = TeamPermissionSerializer,
|
||||
responses = {
|
||||
200: OpenApiResponse(description='Team has been updated with the supplied permissions'),
|
||||
401: OpenApiResponse(description='User Not logged in'),
|
||||
403: OpenApiResponse(description='User is missing permission or in different organization'),
|
||||
}
|
||||
),
|
||||
put = extend_schema(
|
||||
summary = "Amend team Permissions",
|
||||
tags = ['team',],
|
||||
)
|
||||
)
|
||||
class TeamPermissionDetail(views.APIView):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
]
|
||||
|
||||
queryset = Team.objects.all()
|
||||
|
||||
serializer_class = TeamPermissionSerializer
|
||||
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
54
app/api/views/config.py
Normal file
54
app/api/views/config.py
Normal file
@ -0,0 +1,54 @@
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
from rest_framework import generics
|
||||
|
||||
from api.serializers.config import ConfigGroupsSerializer
|
||||
from api.views.mixin import OrganizationPermissionAPI
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Fetch Config groups",
|
||||
description="Returns a list of Config Groups."
|
||||
),
|
||||
)
|
||||
class ConfigGroupsList(generics.ListAPIView):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
]
|
||||
|
||||
queryset = ConfigGroups.objects.all()
|
||||
lookup_field = 'pk'
|
||||
serializer_class = ConfigGroupsSerializer
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
return "Config Groups"
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
get=extend_schema(
|
||||
summary = "Get A Config Group",
|
||||
# responses = {}
|
||||
),
|
||||
)
|
||||
class ConfigGroupsDetail(generics.RetrieveAPIView):
|
||||
|
||||
permission_classes = [
|
||||
OrganizationPermissionAPI
|
||||
]
|
||||
|
||||
queryset = ConfigGroups.objects.all()
|
||||
lookup_field = 'pk'
|
||||
serializer_class = ConfigGroupsSerializer
|
||||
|
||||
|
||||
def get_view_name(self):
|
||||
return "Config Group"
|
||||
|
||||
|
@ -27,6 +27,7 @@ class Index(viewsets.ViewSet):
|
||||
{
|
||||
# "teams": reverse("_api_teams", request=request),
|
||||
"devices": reverse("API:device-list", request=request),
|
||||
"config_groups": reverse("API:_api_config_groups", request=request),
|
||||
"organizations": reverse("API:_api_orgs", request=request),
|
||||
"software": reverse("API:software-list", request=request),
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms import ValidationError
|
||||
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.permissions import DjangoObjectPermissions
|
||||
|
||||
from access.mixin import OrganizationMixin
|
||||
@ -28,12 +29,16 @@ class OrganizationPermissionAPI(DjangoObjectPermissions, OrganizationMixin):
|
||||
|
||||
self.request = request
|
||||
|
||||
method = self.request._request.method.lower()
|
||||
|
||||
if method.upper() not in view.allowed_methods:
|
||||
|
||||
view.http_method_not_allowed(request._request)
|
||||
|
||||
if hasattr(view, 'queryset'):
|
||||
if view.queryset.model._meta:
|
||||
self.obj = view.queryset.model
|
||||
|
||||
method = self.request._request.method.lower()
|
||||
|
||||
object_organization = None
|
||||
|
||||
if method == 'get':
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -307,6 +307,9 @@ curl:
|
||||
'SWAGGER_UI_DIST': 'SIDECAR',
|
||||
'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',
|
||||
'REDOC_DIST': 'SIDECAR',
|
||||
'PREPROCESSING_HOOKS': [
|
||||
'drf_spectacular.hooks.preprocess_exclude_path_format'
|
||||
],
|
||||
}
|
||||
|
||||
DATETIME_FORMAT = 'j N Y H:i:s'
|
||||
|
@ -1,8 +1,46 @@
|
||||
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
|
||||
from core.tests.abstract.models import Models
|
||||
|
||||
|
||||
|
||||
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,
|
||||
Models
|
||||
):
|
||||
""" Test cases for tenancy models"""
|
||||
|
||||
model = None
|
||||
""" Model to test """
|
||||
|
||||
|
||||
|
||||
class ModelAdd(
|
||||
AddView
|
||||
):
|
||||
|
@ -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)
|
||||
)
|
||||
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
@ -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)),
|
@ -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]),
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -0,0 +1,224 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from app.tests.abstract.models import TenancyModel
|
||||
|
||||
from config_management.models.groups import ConfigGroups
|
||||
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class ConfigGroupsAPI(
|
||||
TestCase,
|
||||
):
|
||||
|
||||
model = ConfigGroups
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
2. Create an item
|
||||
|
||||
"""
|
||||
|
||||
self.organization = Organization.objects.create(name='test_org')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one',
|
||||
config = dict({"key": "one", "existing": "dont_over_write"})
|
||||
)
|
||||
|
||||
self.second_item = self.model.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one_two',
|
||||
config = dict({"key": "two"}),
|
||||
parent = self.item
|
||||
)
|
||||
|
||||
self.url_view_kwargs = {'pk': self.second_item.id}
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = self.organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:_api_config_group', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
self.api_data = response.data
|
||||
|
||||
|
||||
def test_api_field_exists_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_parent(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
parent field must exist
|
||||
"""
|
||||
|
||||
assert 'parent' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_parent(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
parent field must be dict
|
||||
"""
|
||||
|
||||
assert type(self.api_data['parent']) is dict
|
||||
|
||||
|
||||
def test_api_field_exists_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
name field must exist
|
||||
"""
|
||||
|
||||
assert 'name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_config(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
config field must exist
|
||||
"""
|
||||
|
||||
assert 'config' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_config(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
config field must be dict
|
||||
"""
|
||||
|
||||
assert type(self.api_data['config']) is dict
|
||||
|
||||
|
||||
def test_api_field_exists_url(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
url field must exist
|
||||
"""
|
||||
|
||||
assert 'url' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_url(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
url field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['url']) is str
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_parent_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
parent.id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data['parent']
|
||||
|
||||
|
||||
def test_api_field_type_parent_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
parent.id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['parent']['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_parent_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
parent.name field must exist
|
||||
"""
|
||||
|
||||
assert 'name' in self.api_data['parent']
|
||||
|
||||
|
||||
def test_api_field_type_parent_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
parent.name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['parent']['name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_parent_url(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
parent.url field must exist
|
||||
"""
|
||||
|
||||
assert 'url' in self.api_data['parent']
|
||||
|
||||
|
||||
def test_api_field_type_parent_url(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
parent.url field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['parent']['url']) is str
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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')
|
||||
|
||||
|
||||
|
||||
|
@ -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)
|
||||
)
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
43
app/core/migrations/0002_notes.py
Normal file
43
app/core/migrations/0002_notes.py
Normal 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'],
|
||||
},
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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]),
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
60
app/core/tests/abstract/models.py
Normal file
60
app/core/tests/abstract/models.py
Normal file
@ -0,0 +1,60 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from access.models import TenancyManager
|
||||
|
||||
|
||||
class Models:
|
||||
""" Test cases for Model Abstract Classes """
|
||||
|
||||
|
||||
|
||||
def test_model_class_tenancy_manager_function_get_queryset(self):
|
||||
""" Function Check
|
||||
|
||||
function `get_queryset()` must exist
|
||||
"""
|
||||
|
||||
assert hasattr(self.model.objects, 'get_queryset')
|
||||
|
||||
assert callable(self.model.objects.get_queryset)
|
||||
|
||||
|
||||
@patch.object(TenancyManager, 'get_queryset')
|
||||
def test_model_class_tenancy_manager_function_get_queryset_called(self, get_queryset):
|
||||
""" Function Check
|
||||
|
||||
function `access.models.TenancyManager.get_queryset()` within the Tenancy manager must
|
||||
be called as this function limits queries to the current users organizations.
|
||||
"""
|
||||
|
||||
self.model.objects.filter()
|
||||
|
||||
assert get_queryset.called
|
||||
|
||||
|
||||
@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
|
||||
|
||||
|
17
app/core/tests/unit/manufacturer/test_manufacturer.py
Normal file
17
app/core/tests/unit/manufacturer/test_manufacturer.py
Normal 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
|
@ -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
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
@ -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),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
@ -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,
|
||||
},
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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]),
|
||||
),
|
||||
]
|
@ -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),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
522
app/itam/tests/unit/device/test_device_api.py
Normal file
522
app/itam/tests/unit/device/test_device_api.py
Normal file
@ -0,0 +1,522 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
from rest_framework.relations import Hyperlink
|
||||
|
||||
from access.models import Organization, Team, TeamUsers, Permission
|
||||
|
||||
from api.tests.abstract.api_permissions import APIPermissions
|
||||
|
||||
from config_management.models.groups import ConfigGroups, ConfigGroupHosts
|
||||
|
||||
from itam.models.device import Device
|
||||
|
||||
|
||||
class DeviceAPI(TestCase):
|
||||
|
||||
|
||||
model = Device
|
||||
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(self):
|
||||
"""Setup Test
|
||||
|
||||
1. Create an organization for user and item
|
||||
. create an organization that is different to item
|
||||
2. Create a device
|
||||
3. create teams with each permission: view, add, change, delete
|
||||
4. create a user per team
|
||||
"""
|
||||
|
||||
organization = Organization.objects.create(name='test_org')
|
||||
|
||||
self.organization = organization
|
||||
|
||||
different_organization = Organization.objects.create(name='test_different_organization')
|
||||
|
||||
|
||||
self.item = self.model.objects.create(
|
||||
organization=organization,
|
||||
name = 'deviceone',
|
||||
uuid = 'val',
|
||||
serial_number = 'another val'
|
||||
)
|
||||
|
||||
config_group = ConfigGroups.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one',
|
||||
config = dict({"key": "one", "existing": "dont_over_write"})
|
||||
)
|
||||
|
||||
config_group_second_item = ConfigGroups.objects.create(
|
||||
organization = self.organization,
|
||||
name = 'one_two',
|
||||
config = dict({"key": "two"}),
|
||||
parent = config_group
|
||||
)
|
||||
|
||||
config_group_hosts = ConfigGroupHosts.objects.create(
|
||||
organization = organization,
|
||||
host = self.item,
|
||||
group = config_group,
|
||||
)
|
||||
|
||||
|
||||
config_group_hosts_two = ConfigGroupHosts.objects.create(
|
||||
organization = organization,
|
||||
host = self.item,
|
||||
group = config_group_second_item,
|
||||
)
|
||||
|
||||
|
||||
# self.url_kwargs = {'pk': self.item.id}
|
||||
|
||||
self.url_view_kwargs = {'pk': self.item.id}
|
||||
|
||||
# self.add_data = {'name': 'device', 'organization': self.organization.id}
|
||||
|
||||
|
||||
view_permissions = Permission.objects.get(
|
||||
codename = 'view_' + self.model._meta.model_name,
|
||||
content_type = ContentType.objects.get(
|
||||
app_label = self.model._meta.app_label,
|
||||
model = self.model._meta.model_name,
|
||||
)
|
||||
)
|
||||
|
||||
view_team = Team.objects.create(
|
||||
team_name = 'view_team',
|
||||
organization = organization,
|
||||
)
|
||||
|
||||
view_team.permissions.set([view_permissions])
|
||||
|
||||
|
||||
|
||||
# add_permissions = Permission.objects.get(
|
||||
# codename = 'add_' + self.model._meta.model_name,
|
||||
# content_type = ContentType.objects.get(
|
||||
# app_label = self.model._meta.app_label,
|
||||
# model = self.model._meta.model_name,
|
||||
# )
|
||||
# )
|
||||
|
||||
# add_team = Team.objects.create(
|
||||
# team_name = 'add_team',
|
||||
# organization = organization,
|
||||
# )
|
||||
|
||||
# add_team.permissions.set([add_permissions])
|
||||
|
||||
|
||||
|
||||
# change_permissions = Permission.objects.get(
|
||||
# codename = 'change_' + self.model._meta.model_name,
|
||||
# content_type = ContentType.objects.get(
|
||||
# app_label = self.model._meta.app_label,
|
||||
# model = self.model._meta.model_name,
|
||||
# )
|
||||
# )
|
||||
|
||||
# change_team = Team.objects.create(
|
||||
# team_name = 'change_team',
|
||||
# organization = organization,
|
||||
# )
|
||||
|
||||
# change_team.permissions.set([change_permissions])
|
||||
|
||||
|
||||
|
||||
# delete_permissions = Permission.objects.get(
|
||||
# codename = 'delete_' + self.model._meta.model_name,
|
||||
# content_type = ContentType.objects.get(
|
||||
# app_label = self.model._meta.app_label,
|
||||
# model = self.model._meta.model_name,
|
||||
# )
|
||||
# )
|
||||
|
||||
# delete_team = Team.objects.create(
|
||||
# team_name = 'delete_team',
|
||||
# organization = organization,
|
||||
# )
|
||||
|
||||
# delete_team.permissions.set([delete_permissions])
|
||||
|
||||
|
||||
# self.no_permissions_user = User.objects.create_user(username="test_no_permissions", password="password")
|
||||
|
||||
|
||||
self.view_user = User.objects.create_user(username="test_user_view", password="password")
|
||||
teamuser = TeamUsers.objects.create(
|
||||
team = view_team,
|
||||
user = self.view_user
|
||||
)
|
||||
|
||||
# self.add_user = User.objects.create_user(username="test_user_add", password="password")
|
||||
# teamuser = TeamUsers.objects.create(
|
||||
# team = add_team,
|
||||
# user = self.add_user
|
||||
# )
|
||||
|
||||
# self.change_user = User.objects.create_user(username="test_user_change", password="password")
|
||||
# teamuser = TeamUsers.objects.create(
|
||||
# team = change_team,
|
||||
# user = self.change_user
|
||||
# )
|
||||
|
||||
# self.delete_user = User.objects.create_user(username="test_user_delete", password="password")
|
||||
# teamuser = TeamUsers.objects.create(
|
||||
# team = delete_team,
|
||||
# user = self.delete_user
|
||||
# )
|
||||
|
||||
|
||||
# self.different_organization_user = User.objects.create_user(username="test_different_organization_user", password="password")
|
||||
|
||||
|
||||
# different_organization_team = Team.objects.create(
|
||||
# team_name = 'different_organization_team',
|
||||
# organization = different_organization,
|
||||
# )
|
||||
|
||||
# different_organization_team.permissions.set([
|
||||
# view_permissions,
|
||||
# add_permissions,
|
||||
# change_permissions,
|
||||
# delete_permissions,
|
||||
# ])
|
||||
|
||||
# TeamUsers.objects.create(
|
||||
# team = different_organization_team,
|
||||
# user = self.different_organization_user
|
||||
# )
|
||||
|
||||
|
||||
client = Client()
|
||||
url = reverse('API:device-detail', kwargs=self.url_view_kwargs)
|
||||
|
||||
|
||||
client.force_login(self.view_user)
|
||||
response = client.get(url)
|
||||
|
||||
self.api_data = response.data
|
||||
|
||||
|
||||
def test_api_field_exists_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_is_global(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
is_global field must exist
|
||||
"""
|
||||
|
||||
assert 'is_global' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_is_global(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
is_global field must be boolean
|
||||
"""
|
||||
|
||||
assert type(self.api_data['is_global']) is bool
|
||||
|
||||
|
||||
def test_api_field_exists_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
name field must exist
|
||||
"""
|
||||
|
||||
assert 'name' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_config(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
config field must exist
|
||||
"""
|
||||
|
||||
assert 'config' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_config(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
config field must be dict
|
||||
"""
|
||||
|
||||
assert type(self.api_data['config']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_serial_number(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
serial_number field must exist
|
||||
"""
|
||||
|
||||
assert 'serial_number' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_serial_number(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
serial_number field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['serial_number']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_uuid(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
uuid field must exist
|
||||
"""
|
||||
|
||||
assert 'uuid' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_uuid(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
uuid field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['uuid']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_inventorydate(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
inventorydate field must exist
|
||||
"""
|
||||
|
||||
assert 'inventorydate' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_inventorydate(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
inventorydate field must be str
|
||||
"""
|
||||
|
||||
assert (
|
||||
type(self.api_data['inventorydate']) is str
|
||||
or
|
||||
self.api_data['inventorydate'] is None
|
||||
)
|
||||
|
||||
|
||||
def test_api_field_exists_created(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
created field must exist
|
||||
"""
|
||||
|
||||
assert 'created' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_created(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
created field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['created']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_modified(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
modified field must exist
|
||||
"""
|
||||
|
||||
assert 'modified' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_modified(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
modified field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['modified']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_groups(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
groups field must exist
|
||||
"""
|
||||
|
||||
assert 'groups' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_groups(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
groups field must be list
|
||||
"""
|
||||
|
||||
assert type(self.api_data['groups']) is list
|
||||
|
||||
|
||||
def test_api_field_exists_organization(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
organization field must exist
|
||||
"""
|
||||
|
||||
assert 'organization' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_organization(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
organization field must be dict
|
||||
"""
|
||||
|
||||
assert type(self.api_data['organization']) is dict
|
||||
|
||||
|
||||
def test_api_field_exists_url(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
url field must exist
|
||||
"""
|
||||
|
||||
assert 'url' in self.api_data
|
||||
|
||||
|
||||
def test_api_field_type_url(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
url field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['url']) is Hyperlink
|
||||
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_organization_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
organization.id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data['organization']
|
||||
|
||||
|
||||
def test_api_field_type_organization_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
organization.id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['organization']['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_organization_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
organization.name field must exist
|
||||
"""
|
||||
|
||||
assert 'name' in self.api_data['organization']
|
||||
|
||||
|
||||
def test_api_field_type_organization_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
organization.name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['organization']['name']) is str
|
||||
|
||||
|
||||
|
||||
|
||||
def test_api_field_exists_groups_id(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
groups.id field must exist
|
||||
"""
|
||||
|
||||
assert 'id' in self.api_data['groups'][0]
|
||||
|
||||
|
||||
def test_api_field_type_groups_id(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
groups.id field must be int
|
||||
"""
|
||||
|
||||
assert type(self.api_data['groups'][0]['id']) is int
|
||||
|
||||
|
||||
def test_api_field_exists_groups_name(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
groups.name field must exist
|
||||
"""
|
||||
|
||||
assert 'name' in self.api_data['groups'][0]
|
||||
|
||||
|
||||
def test_api_field_type_groups_name(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
groups.name field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['groups'][0]['name']) is str
|
||||
|
||||
|
||||
def test_api_field_exists_groups_url(self):
|
||||
""" Test for existance of API Field
|
||||
|
||||
groups.url field must exist
|
||||
"""
|
||||
|
||||
assert 'url' in self.api_data['groups'][0]
|
||||
|
||||
|
||||
def test_api_field_type_groups_url(self):
|
||||
""" Test for type for API Field
|
||||
|
||||
groups.url field must be str
|
||||
"""
|
||||
|
||||
assert type(self.api_data['groups'][0]['url']) is Hyperlink
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
17
app/itam/tests/unit/device_type/test_device_type.py
Normal file
17
app/itam/tests/unit/device_type/test_device_type.py
Normal 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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
||||
|
@ -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
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user