Skip to content

Commit

Permalink
Merge pull request #342 from nofusscomputing/development
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-nfc authored Oct 11, 2024
2 parents c653197 + 28f51d3 commit c59e64c
Show file tree
Hide file tree
Showing 260 changed files with 22,121 additions and 328 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ artifacts/
volumes/
build/
pages/
node_modules/
.markdownlint-cli2.jsonc
.markdownlint.json
package-lock.json
package.json
**.junit.xml
70 changes: 70 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,76 @@
# Contribution Guide


Development of this project has been setup to be done from VSCodium. The following additional requirements need to be met:

- npm has been installed. _required for `markdown` linting_

`sudo apt install -y --no-install-recommends npm`

- setup of other requirements can be done with `make prepare`

- **ALL** Linting must pass for Merge to be conducted.

_`make lint`_

## TL;DR


from the root of the project to start a test server use:

``` bash

# activate python venv
source /tmp/centurion_erp/bin/activate

# enter app dir
cd app

# Start dev server can be viewed at http://127.0.0.1:8002
python manage.py runserver 8002

# Run any migrations, if required
python manage.py migrate

# Create a super suer if required
python manage.py createsuperuser

```

## Makefile

!!! tip "TL;DR"
Common make commands are `make prepare` then `make docs` and `make lint`

Included within the root of the repository is a makefile that can be used during development to check/run different items as is required during development. The following make targets are available:

- `prepare`

_prepare the repository. init's all git submodules and sets up a python virtual env and other make targets_

- `docs`

_builds the docs and places them within a directory called build, which can be viewed within a web browser_

- `lint`

_conducts all required linting_

- `docs-lint`

_lints the markdown documents within the docs directory for formatting errors that MKDocs may/will have an issue with._

- `clean`

_cleans up build artifacts and removes the python virtual environment_


> this doc is yet to receive a re-write

# Old working docs


## Dev Environment

It's advised to setup a python virtual env for development. this can be done with the following commands.
Expand Down
1 change: 1 addition & 0 deletions app/access/functions/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def permission_queryset():
exclude_models = [
'appsettings',
'chordcounter',
'comment',
'groupresult',
'organization'
'settings',
Expand Down
77 changes: 69 additions & 8 deletions app/access/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@
class OrganizationMixin():
"""Base Organization class"""

parent_model: str = None
""" Parent Model
This attribute defines the parent model for the model in question. The parent model when defined
will be used as the object to obtain the permissions from.
"""

parent_model_pk_kwarg: str = 'pk'
"""Parent Model kwarg
This value is used to define the kwarg that is used as the parent objects primary key (pk).
"""

request = None

user_groups = []
Expand All @@ -26,20 +39,24 @@ def get_parent_obj(self):
parent_model (Model): with PK from kwargs['pk']
"""

return self.parent_model.objects.get(pk=self.kwargs['pk'])
return self.parent_model.objects.get(pk=self.kwargs[self.parent_model_pk_kwarg])


def object_organization(self) -> int:

id = None

if hasattr(self, '_object_organization'):

return self._object_organization

try:

if hasattr(self, 'get_queryset'):
self.get_queryset()


if hasattr(self, 'parent_model'):
if self.parent_model:
obj = self.get_parent_obj()

id = obj.get_organization().id
Expand All @@ -61,6 +78,10 @@ def object_organization(self) -> int:

id = 0

if hasattr(self, 'instance') and id is None: # Form Instance

id = self.instance.get_organization()


except AttributeError:

Expand All @@ -84,6 +105,10 @@ def object_organization(self) -> int:

pass

if id is not None:

self._object_organization = id


return id

Expand Down Expand Up @@ -147,6 +172,10 @@ def user_organizations(self) -> list():

user_organizations = []

if hasattr(self, '_user_organizations'):

return self._user_organizations

teams = Team.objects

for group in self.request.user.groups.all():
Expand All @@ -157,14 +186,32 @@ def user_organizations(self) -> list():

user_organizations = user_organizations + [team.organization.id]

if len(user_organizations) > 0:

self._user_organizations = user_organizations


return user_organizations


# ToDo: Ensure that the group has access to item
def has_organization_permission(self, organization: int=None) -> bool:
def has_organization_permission(self, organization: int = None, permissions_required: list = None) -> bool:
""" Check if user has permission within organization.
Args:
organization (int, optional): Organization to check. Defaults to None.
permissions_required (list, optional): if doing object level permissions, pass in required permission. Defaults to None.
Returns:
bool: True for yes.
"""

has_permission = False

if permissions_required is None:

permissions_required = self.get_permission_required()

if not organization:

organization = self.object_organization()
Expand All @@ -182,7 +229,7 @@ def has_organization_permission(self, organization: int=None) -> bool:

assembled_permission = str(permission["content_type__app_label"]) + '.' + str(permission["codename"])

if assembled_permission in self.get_permission_required() and (team['organization_id'] == organization or organization == 0):
if assembled_permission in permissions_required and (team['organization_id'] == organization or organization == 0):

return True

Expand Down Expand Up @@ -242,16 +289,24 @@ def permission_check(self, request, permissions_required: list = None) -> bool:

return True

perms = self.get_permission_required()
if permissions_required:

if self.has_organization_permission():
perms = permissions_required

return True
else:

if self.request.user.has_perms(perms) and len(self.kwargs) == 0 and str(self.request.method).lower() == 'get':
perms = self.get_permission_required()

if self.has_organization_permission(permissions_required = perms):

return True

if self.request.user.has_perms(perms) and str(self.request.method).lower() == 'get':

if len(self.kwargs) == 0 or (len(self.kwargs) == 1 and 'ticket_type' in self.kwargs):

return True

for required_permission in self.permission_required:

if required_permission.replace(
Expand Down Expand Up @@ -327,6 +382,12 @@ def dispatch(self, request, *args, **kwargs):

if not request.user.is_authenticated:
return self.handle_no_permission()

if len(self.permission_required) == 0:

if hasattr(self, 'get_dynamic_permissions'):

self.permission_required = self.get_dynamic_permissions()

if len(self.permission_required) > 0:

Expand Down
29 changes: 23 additions & 6 deletions app/access/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,21 @@ def get_queryset(self):
user_organizations += [ team_user.team.organization.id ]


if len(user_organizations) > 0 and not user.is_superuser:
if len(user_organizations) > 0 and not user.is_superuser and self.model.is_global is not None:

return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
|
models.Q(is_global = True)
)
if self.model.is_global:

return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
|
models.Q(is_global = True)
)

else:

return super().get_queryset().filter(
models.Q(organization__in=user_organizations)
)

return super().get_queryset()

Expand Down Expand Up @@ -188,6 +196,15 @@ def validatate_organization_exists(self):
def get_organization(self) -> Organization:
return self.organization


def save(self, force_insert=False, force_update=False, using=None, update_fields=None):

if self.organization is None:

raise ValidationError('Organization not defined')

super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)



class Team(Group, TenancyObject):
Expand Down
13 changes: 13 additions & 0 deletions app/access/tests/abstract/tenancy_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ class TenancyObject:
model = None
""" Model to be tested """

should_model_history_be_saved: bool = True
""" Should model history be saved.
By default this should always be 'True', however in special
circumstances, this may not be desired.
"""


def test_history_save(self):
"""Confirm the desired intent for saving model history."""

assert self.model.save_model_history == self.should_model_history_be_saved


def test_has_attr_get_organization(self):
""" TenancyObject attribute check
Expand Down
10 changes: 10 additions & 0 deletions app/access/tests/unit/tenancy_object/test_tenancy_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,13 @@ def test_attribute_not_none_objects(self):
"""

assert self.item.objects is not None


@pytest.mark.skip(reason="write test")
def test_field_not_none_organzation(self):
""" Ensure field is set
Field organization must be defined for all tenancy objects
"""

assert self.item.objects is not None
Loading

0 comments on commit c59e64c

Please sign in to comment.