@ -79,38 +79,6 @@ class TenancyAbstractModel(
|
||||
ValidationError: User failed to supply organization
|
||||
"""
|
||||
|
||||
def __init____init__(self, *args, **kwargs):
|
||||
|
||||
self.context: dict = {
|
||||
'logger': None,
|
||||
'user': None,
|
||||
}
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
context: dict = {
|
||||
'logger': None,
|
||||
'user': None,
|
||||
}
|
||||
""" Model Context
|
||||
|
||||
Generally model usage will be from an API serializer, Admin Site or
|
||||
a management command. These sources are to pass through and set this
|
||||
context. The keys are:
|
||||
|
||||
!!! warning
|
||||
Failing to specify the user will prevent the tenancy manager from
|
||||
being multi-tenant. As such, the results retured will not be
|
||||
restricted to the users tenancy
|
||||
|
||||
returns:
|
||||
logger (logging.Logger): Instance of a logger for logging.
|
||||
user (User): The user that is logged into the system
|
||||
|
||||
Context for actions within the model.
|
||||
"""
|
||||
|
||||
objects = TenancyManagerDepreciated()
|
||||
""" ~~Multi-Tenant Manager~~
|
||||
|
||||
|
@ -14,15 +14,7 @@ class TenancyAbstractModelTestCases(
|
||||
):
|
||||
|
||||
|
||||
parameterized_class_attributes = {
|
||||
'context': {
|
||||
'type': dict,
|
||||
# 'value': {
|
||||
# 'logger': None,
|
||||
# 'user': None,
|
||||
# }
|
||||
}
|
||||
}
|
||||
parameterized_class_attributes = {}
|
||||
|
||||
|
||||
parameterized_model_fields = {
|
||||
|
@ -279,6 +279,15 @@ def pytest_generate_tests(metafunc):
|
||||
)
|
||||
|
||||
|
||||
else:
|
||||
|
||||
pytest.mark.xfail(
|
||||
reason = 'No Parameters for parameterized test'
|
||||
)(
|
||||
getattr(metafunc.cls, metafunc.definition.name)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def create_model(request, django_db_blocker):
|
||||
|
303
app/core/mixins/centurion.py
Normal file
303
app/core/mixins/centurion.py
Normal file
@ -0,0 +1,303 @@
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
|
||||
class Centurion(
|
||||
models.Model
|
||||
):
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
abstract = True
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
self.context: dict = {
|
||||
'logger': None,
|
||||
'user': None,
|
||||
}
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
_audit_enabled: bool = True
|
||||
"""Should this model have audit history kept"""
|
||||
|
||||
_is_submodel: bool = False
|
||||
"""This model a sub-model"""
|
||||
|
||||
_notes_enabled: bool = True
|
||||
"""Should a table for notes be created for this model"""
|
||||
|
||||
app_namespace: str = None
|
||||
"""URL Application namespace.
|
||||
|
||||
**Note:** This attribute is a temp attribute until all models urls return
|
||||
to their own `urls.py` file from `api/urls_v2.py`.
|
||||
"""
|
||||
|
||||
|
||||
context: dict = {
|
||||
'logger': None,
|
||||
'user': None,
|
||||
}
|
||||
""" Model Context
|
||||
|
||||
Generally model usage will be from an API serializer, Admin Site or
|
||||
a management command. These sources are to pass through and set this
|
||||
context. The keys are:
|
||||
|
||||
!!! warning
|
||||
Failing to specify the user will prevent the tenancy manager from
|
||||
being multi-tenant. As such, the results retured will not be
|
||||
restricted to the users tenancy
|
||||
|
||||
returns:
|
||||
logger (logging.Logger): Instance of a logger for logging.
|
||||
user (User): The user that is logged into the system
|
||||
|
||||
Context for actions within the model.
|
||||
"""
|
||||
|
||||
model_tag: str = None
|
||||
"""Model Tag
|
||||
|
||||
String that is used as this models tag. Used within ticketing for linking a
|
||||
model to a ticket and wihin markdown for referencing a model.
|
||||
"""
|
||||
|
||||
url_model_name: str = None
|
||||
"""URL Model Name override
|
||||
|
||||
Optionally use this attribute to set the model name for the url `basename`,
|
||||
i.e. `_api_<url_model_name>`
|
||||
"""
|
||||
|
||||
|
||||
def delete(self, using = None, keep_parents = None):
|
||||
"""Delete Centurion Model
|
||||
|
||||
If a model has `_audit_enabled = True`, audit history is populated and
|
||||
ready to be saved by the audit system (save signal.).
|
||||
|
||||
Args:
|
||||
using (_type_, optional): _description_. Defaults to None.
|
||||
keep_parents (bool, optional): Keep parent models. Defaults to the
|
||||
value if is_submodel so as not to delete parent models.
|
||||
"""
|
||||
|
||||
if keep_parents is None:
|
||||
keep_parents = self._is_submodel
|
||||
|
||||
if self._audit_enabled:
|
||||
|
||||
self._after = {}
|
||||
|
||||
self._before = type(self).objects.get( id = self.id ).get_audit_values()
|
||||
|
||||
|
||||
super().delete(using = using, keep_parents = keep_parents)
|
||||
|
||||
|
||||
|
||||
def full_clean(self, exclude = None,
|
||||
validate_unique = True, validate_constraints = True
|
||||
) -> None:
|
||||
|
||||
super().full_clean(
|
||||
exclude = exclude,
|
||||
validate_unique = validate_unique,
|
||||
validate_constraints = validate_constraints
|
||||
)
|
||||
|
||||
|
||||
def get_app_namespace(self) -> str:
|
||||
"""Fetch the Application namespace if specified.
|
||||
|
||||
**Note:** This attribute is a temp attribute until all models urls return
|
||||
to their own `urls.py` file from `api/urls_v2.py`.
|
||||
|
||||
Returns:
|
||||
str: Application namespace suffixed with colin `:`
|
||||
None: No application namespace found.
|
||||
"""
|
||||
|
||||
if not self.app_namespace:
|
||||
return None
|
||||
|
||||
app_namespace = self.app_namespace
|
||||
|
||||
return str(app_namespace)
|
||||
|
||||
|
||||
def get_audit_values(self) -> dict:
|
||||
"""Retrieve the field Values
|
||||
|
||||
Currently ensures only fields are present.
|
||||
|
||||
**ToDo:** Update so the dict that it returns is a dict of dict where each dict
|
||||
is named after the actual models the fields come from and it contains
|
||||
only it's fields.
|
||||
|
||||
Returns:
|
||||
dict: Model fields
|
||||
"""
|
||||
|
||||
data = self.__dict__.copy()
|
||||
|
||||
clean_data: dict = {}
|
||||
|
||||
for field in self._meta.fields:
|
||||
|
||||
if hasattr(self, field.name):
|
||||
|
||||
clean_data.update({
|
||||
field.name: getattr(self, field.name)
|
||||
})
|
||||
|
||||
|
||||
return clean_data
|
||||
|
||||
|
||||
|
||||
def get_after(self) -> dict:
|
||||
"""Audit Data After Change
|
||||
|
||||
Returns:
|
||||
dict: All model fields after the data changed
|
||||
"""
|
||||
return self._after
|
||||
|
||||
|
||||
|
||||
def get_before(self) -> dict:
|
||||
"""Audit Data Before Change
|
||||
|
||||
Returns:
|
||||
dict: All model fields before the data changed
|
||||
"""
|
||||
return self._before
|
||||
|
||||
|
||||
|
||||
def get_history_model_name(self) -> str:
|
||||
"""Get the name for the History Model
|
||||
|
||||
Returns:
|
||||
str: Name of the history model (`<model class name>AuditHistory`)
|
||||
"""
|
||||
|
||||
return f'{self._meta.object_name}AuditHistory'
|
||||
|
||||
|
||||
|
||||
def get_url(
|
||||
self, relative: bool = False, api_version: int = 2, many = False, request: any = None
|
||||
) -> str:
|
||||
"""Return the models API URL
|
||||
|
||||
Args:
|
||||
relative (bool, optional): Return the relative URL for the model. Defaults to False.
|
||||
api_version (int, optional): API Version to use. Defaults to `2``.
|
||||
request (any, optional): Temp and unused attribute until rest of
|
||||
codebase re-written not to pass through.
|
||||
|
||||
Returns:
|
||||
str: API URL for the model
|
||||
"""
|
||||
|
||||
namespace = f'v{api_version}'
|
||||
|
||||
if self.get_app_namespace():
|
||||
namespace = namespace + ':' + self.get_app_namespace()
|
||||
|
||||
|
||||
url_basename = f'{namespace}:_api_{self._meta.model_name}'
|
||||
|
||||
if self.url_model_name:
|
||||
|
||||
url_basename = f'{namespace}:_api_{self.url_model_name}'
|
||||
|
||||
if self._is_submodel:
|
||||
|
||||
url_basename += '_sub'
|
||||
|
||||
|
||||
if many:
|
||||
|
||||
url_basename += '-list'
|
||||
|
||||
else:
|
||||
|
||||
url_basename += '-detail'
|
||||
|
||||
|
||||
url = reverse( viewname = url_basename, kwargs = self.get_url_kwargs( many = many ) )
|
||||
|
||||
if not relative:
|
||||
|
||||
url = settings.SITE_URL + url
|
||||
|
||||
|
||||
return url
|
||||
|
||||
|
||||
|
||||
def get_url_kwargs(self, many = False) -> dict:
|
||||
"""Get URL Kwargs
|
||||
|
||||
Fecth the kwargs required for building a models URL using the reverse
|
||||
method.
|
||||
|
||||
**Note:** It's advisable that if you override this function, that you
|
||||
call it's super, so as not to duplicate code. That way each override
|
||||
builds up[on the parent `get_url_kwargs` function.
|
||||
|
||||
Returns:
|
||||
dict: Kwargs required for reverse function to build a models URL.
|
||||
"""
|
||||
|
||||
if many:
|
||||
return {}
|
||||
else:
|
||||
return { 'pk': self.id }
|
||||
|
||||
|
||||
|
||||
def save(self, force_insert = False, force_update = False, using = None, update_fields = None):
|
||||
"""Save Centurion Model
|
||||
|
||||
This Save ensures that `full_clean()` is called so that prior to the
|
||||
model being saved to the database, it is valid.
|
||||
|
||||
If a model has `_audit_enabled = True`, audit history is populated and
|
||||
ready to be saved by the audit system (save signal.).
|
||||
"""
|
||||
|
||||
self.full_clean(
|
||||
exclude = None,
|
||||
validate_unique = True,
|
||||
validate_constraints = True
|
||||
)
|
||||
|
||||
if self._audit_enabled and self.context['user']:
|
||||
|
||||
self._after = self.get_audit_values()
|
||||
|
||||
if self.id:
|
||||
|
||||
self._before = type(self).objects.get( id = self.id ).get_audit_values()
|
||||
|
||||
else:
|
||||
|
||||
self._before = {}
|
||||
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update,
|
||||
using=using, update_fields=update_fields
|
||||
)
|
@ -1,4 +1,3 @@
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import (
|
||||
ValidationError
|
||||
)
|
||||
@ -7,47 +6,16 @@ from django.db import models
|
||||
from access.fields import AutoCreatedField
|
||||
from access.models.tenancy_abstract import TenancyAbstractModel
|
||||
|
||||
from rest_framework.reverse import reverse
|
||||
from core.mixins.centurion import Centurion
|
||||
|
||||
|
||||
|
||||
class CenturionModel(
|
||||
Centurion,
|
||||
TenancyAbstractModel,
|
||||
):
|
||||
|
||||
|
||||
|
||||
_audit_enabled: bool = True
|
||||
"""Should this model have audit history kept"""
|
||||
|
||||
_is_submodel: bool = False
|
||||
"""This model a sub-model"""
|
||||
|
||||
_notes_enabled: bool = True
|
||||
"""Should a table for notes be created for this model"""
|
||||
|
||||
app_namespace: str = None
|
||||
"""URL Application namespace.
|
||||
|
||||
**Note:** This attribute is a temp attribute until all models urls return
|
||||
to their own `urls.py` file from `api/urls_v2.py`.
|
||||
"""
|
||||
|
||||
model_tag: str = None
|
||||
"""Model Tag
|
||||
|
||||
String that is used as this models tag. Used within ticketing for linking a
|
||||
model to a ticket and wihin markdown for referencing a model.
|
||||
"""
|
||||
|
||||
url_model_name: str = None
|
||||
"""URL Model Name override
|
||||
|
||||
Optionally use this attribute to set the model name for the url `basename`,
|
||||
i.e. `_api_<url_model_name>`
|
||||
"""
|
||||
|
||||
|
||||
class Meta:
|
||||
|
||||
abstract = True
|
||||
@ -84,229 +52,6 @@ class CenturionModel(
|
||||
|
||||
|
||||
|
||||
def delete(self, using = None, keep_parents = None):
|
||||
"""Delete Centurion Model
|
||||
|
||||
If a model has `_audit_enabled = True`, audit history is populated and
|
||||
ready to be saved by the audit system (save signal.).
|
||||
|
||||
Args:
|
||||
using (_type_, optional): _description_. Defaults to None.
|
||||
keep_parents (bool, optional): Keep parent models. Defaults to the
|
||||
value if is_submodel so as not to delete parent models.
|
||||
"""
|
||||
|
||||
if keep_parents is None:
|
||||
keep_parents = self._is_submodel
|
||||
|
||||
if self._audit_enabled:
|
||||
|
||||
self._after = {}
|
||||
|
||||
self._before = type(self).objects.get( id = self.id ).get_audit_values()
|
||||
|
||||
|
||||
super().delete(using = using, keep_parents = keep_parents)
|
||||
|
||||
|
||||
|
||||
def full_clean(self, exclude = None,
|
||||
validate_unique = True, validate_constraints = True
|
||||
) -> None:
|
||||
|
||||
super().full_clean(
|
||||
exclude = exclude,
|
||||
validate_unique = validate_unique,
|
||||
validate_constraints = validate_constraints
|
||||
)
|
||||
|
||||
|
||||
def get_app_namespace(self) -> str:
|
||||
"""Fetch the Application namespace if specified.
|
||||
|
||||
**Note:** This attribute is a temp attribute until all models urls return
|
||||
to their own `urls.py` file from `api/urls_v2.py`.
|
||||
|
||||
Returns:
|
||||
str: Application namespace suffixed with colin `:`
|
||||
None: No application namespace found.
|
||||
"""
|
||||
|
||||
if not self.app_namespace:
|
||||
return None
|
||||
|
||||
app_namespace = self.app_namespace
|
||||
|
||||
return str(app_namespace)
|
||||
|
||||
|
||||
def get_audit_values(self) -> dict:
|
||||
"""Retrieve the field Values
|
||||
|
||||
Currently ensures only fields are present.
|
||||
|
||||
**ToDo:** Update so the dict that it returns is a dict of dict where each dict
|
||||
is named after the actual models the fields come from and it contains
|
||||
only it's fields.
|
||||
|
||||
Returns:
|
||||
dict: Model fields
|
||||
"""
|
||||
|
||||
data = self.__dict__.copy()
|
||||
|
||||
clean_data: dict = {}
|
||||
|
||||
for field in self._meta.fields:
|
||||
|
||||
if hasattr(self, field.name):
|
||||
|
||||
clean_data.update({
|
||||
field.name: getattr(self, field.name)
|
||||
})
|
||||
|
||||
|
||||
return clean_data
|
||||
|
||||
|
||||
|
||||
def get_after(self) -> dict:
|
||||
"""Audit Data After Change
|
||||
|
||||
Returns:
|
||||
dict: All model fields after the data changed
|
||||
"""
|
||||
return self._after
|
||||
|
||||
|
||||
|
||||
def get_before(self) -> dict:
|
||||
"""Audit Data Before Change
|
||||
|
||||
Returns:
|
||||
dict: All model fields before the data changed
|
||||
"""
|
||||
return self._before
|
||||
|
||||
|
||||
|
||||
def get_history_model_name(self) -> str:
|
||||
"""Get the name for the History Model
|
||||
|
||||
Returns:
|
||||
str: Name of the history model (`<model class name>AuditHistory`)
|
||||
"""
|
||||
|
||||
return f'{self._meta.object_name}AuditHistory'
|
||||
|
||||
|
||||
|
||||
def get_url( self, relative: bool = False, api_version: int = 2, many = False, request: any = None ) -> str:
|
||||
"""Return the models API URL
|
||||
|
||||
Args:
|
||||
relative (bool, optional): Return the relative URL for the model. Defaults to False.
|
||||
api_version (int, optional): API Version to use. Defaults to `2``.
|
||||
request (any, optional): Temp and unused attribute until rest of
|
||||
codebase re-written not to pass through.
|
||||
|
||||
Returns:
|
||||
str: API URL for the model
|
||||
"""
|
||||
|
||||
namespace = f'v{api_version}'
|
||||
|
||||
if self.get_app_namespace():
|
||||
namespace = namespace + ':' + self.get_app_namespace()
|
||||
|
||||
|
||||
url_basename = f'{namespace}:_api_{self._meta.model_name}'
|
||||
|
||||
if self.url_model_name:
|
||||
|
||||
url_basename = f'{namespace}:_api_{self.url_model_name}'
|
||||
|
||||
if self._is_submodel:
|
||||
|
||||
url_basename += '_sub'
|
||||
|
||||
|
||||
if many:
|
||||
|
||||
url_basename += '-list'
|
||||
|
||||
else:
|
||||
|
||||
url_basename += '-detail'
|
||||
|
||||
|
||||
url = reverse( viewname = url_basename, kwargs = self.get_url_kwargs( many = many ) )
|
||||
|
||||
if not relative:
|
||||
|
||||
url = settings.SITE_URL + url
|
||||
|
||||
|
||||
return url
|
||||
|
||||
|
||||
|
||||
def get_url_kwargs(self, many = False) -> dict:
|
||||
"""Get URL Kwargs
|
||||
|
||||
Fecth the kwargs required for building a models URL using the reverse
|
||||
method.
|
||||
|
||||
**Note:** It's advisable that if you override this function, that you
|
||||
call it's super, so as not to duplicate code. That way each override
|
||||
builds up[on the parent `get_url_kwargs` function.
|
||||
|
||||
Returns:
|
||||
dict: Kwargs required for reverse function to build a models URL.
|
||||
"""
|
||||
|
||||
if many:
|
||||
return {}
|
||||
else:
|
||||
return { 'pk': self.id }
|
||||
|
||||
|
||||
|
||||
def save(self, force_insert = False, force_update = False, using = None, update_fields = None):
|
||||
"""Save Centurion Model
|
||||
|
||||
This Save ensures that `full_clean()` is called so that prior to the
|
||||
model being saved to the database, it is valid.
|
||||
|
||||
If a model has `_audit_enabled = True`, audit history is populated and
|
||||
ready to be saved by the audit system (save signal.).
|
||||
"""
|
||||
|
||||
self.full_clean(
|
||||
exclude = None,
|
||||
validate_unique = True,
|
||||
validate_constraints = True
|
||||
)
|
||||
|
||||
if self._audit_enabled and self.context['user']:
|
||||
|
||||
self._after = self.get_audit_values()
|
||||
|
||||
if self.id:
|
||||
|
||||
self._before = type(self).objects.get( id = self.id ).get_audit_values()
|
||||
|
||||
else:
|
||||
|
||||
self._before = {}
|
||||
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update,
|
||||
using=using, update_fields=update_fields
|
||||
)
|
||||
|
||||
|
||||
|
||||
class CenturionSubModel(
|
||||
CenturionModel
|
||||
):
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from django.db import models
|
||||
|
||||
from core.tests.unit.centurion_abstract.test_unit_centurion_abstract_model import (
|
||||
CenturionAbstractModelTestCases,
|
||||
CenturionAbstractModelInheritedCases,
|
||||
@ -94,6 +96,14 @@ class CenturionSubAbstractModelPyTest(
|
||||
def parameterized_class_attributes(self):
|
||||
|
||||
return {
|
||||
'page_layout': {
|
||||
'type': models.NOT_PROVIDED,
|
||||
'value': models.NOT_PROVIDED,
|
||||
},
|
||||
'table_fields': {
|
||||
'type': models.NOT_PROVIDED,
|
||||
'value': models.NOT_PROVIDED,
|
||||
},
|
||||
'model_tag': {
|
||||
'type': type(None),
|
||||
'value': None,
|
||||
|
16
app/core/tests/unit/mixin_centurion/conftest.py
Normal file
16
app/core/tests/unit/mixin_centurion/conftest.py
Normal file
@ -0,0 +1,16 @@
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def model(mixin_centurion):
|
||||
|
||||
yield mixin_centurion
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class', autouse = True)
|
||||
def model_kwargs(request):
|
||||
|
||||
request.cls.kwargs_create_item = {}
|
||||
|
||||
yield {}
|
1185
app/core/tests/unit/mixin_centurion/test_unit_centurion_mixin.py
Normal file
1185
app/core/tests/unit/mixin_centurion/test_unit_centurion_mixin.py
Normal file
File diff suppressed because it is too large
Load Diff
4
app/tests/fixtures/__init__.py
vendored
4
app/tests/fixtures/__init__.py
vendored
@ -9,6 +9,10 @@ from .kwargs_api_create import (
|
||||
kwargs_api_create
|
||||
)
|
||||
|
||||
from .mixin_centurion import (
|
||||
mixin_centurion,
|
||||
)
|
||||
|
||||
from .model_centurionaudit import (
|
||||
kwargs_centurionaudit,
|
||||
model_centurionaudit,
|
||||
|
10
app/tests/fixtures/mixin_centurion.py
vendored
Normal file
10
app/tests/fixtures/mixin_centurion.py
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
import pytest
|
||||
|
||||
from core.mixins.centurion import Centurion
|
||||
|
||||
|
||||
|
||||
@pytest.fixture( scope = 'class')
|
||||
def mixin_centurion():
|
||||
|
||||
yield Centurion
|
@ -1071,6 +1071,8 @@ markers = [
|
||||
"centurion_models: Selects Centurion models",
|
||||
"functional: Selects all Functional tests.",
|
||||
"meta_models: Selects Meta models",
|
||||
"mixin: Selects all mixin test cases.",
|
||||
"mixin_centurion: Selects all centurion mixin test cases.",
|
||||
"model_configgroups: Selects Config Group tests.",
|
||||
"model_configgrouphosts: Selects Config Group Hosts tests.",
|
||||
"model_configgroupsoftware: Selects Config Group Software tests.",
|
||||
|
Reference in New Issue
Block a user