From 198fbe3c1e73436ac40230cb4ceeb7a83536248e Mon Sep 17 00:00:00 2001 From: Kevin Thorne Date: Tue, 15 Dec 2020 12:34:41 -0700 Subject: [PATCH] Removed print statements, added view columns to receipts. Updated everything for release --- README.md | 23 ++- docs/index.md | 27 ++- docs/lib/cli/import_csv.html | 2 +- docs/lib/layer/security.html | 294 ++++++++++++++++++++++++++- docs/lib/model/change_request.html | 209 ++++++++++++++++--- docs/lib/model/employee.html | 2 +- docs/lib/model/index.html | 31 ++- docs/lib/model/receipt.html | 21 ++ docs/lib/repository/db.html | 2 +- docs/lib/utils.html | 10 +- docs/ui/control/change_requests.html | 21 +- docs/ui/control/database.html | 29 +-- docs/ui/control/login.html | 9 +- docs/ui/store/index.html | 2 +- docs/ui/window/change_requests.html | 67 +++++- docs/ui/window/database.html | 106 +++++----- lib/layer/security.py | 2 +- lib/model/__init__.py | 1 - lib/model/change_request.py | 2 - lib/model/receipt.py | 8 + lib/model/timesheet.py | 2 +- ui/control/database.py | 2 - ui/window/database.py | 1 - 23 files changed, 719 insertions(+), 154 deletions(-) diff --git a/README.md b/README.md index e4ed267..b798cb3 100644 --- a/README.md +++ b/README.md @@ -21,16 +21,24 @@ However, EmpDat can be run on any operating system Python is supported on. ### The easy way ##### Windows: -1. ~~Run the installer wizard in win/EmpDatSetup.exe~~ - - Due to security issues with Windows, an installation wizard is not possible. -1. Unzip the `Portable.exe.zip` to a location of choice. +1. Unzip the `Portable.win.zip` to a location of choice. 2. Run `EmpDat.exe` 3. Done! -#### The hard way: from source +#### The harder way: from included environment Most of the steps require a terminal window. -1. Acquire the source from either the deployment ZIP archive - - `src/` is the source folder within the deployment ZIP archive +1. Acquire the source with dependencies from the deployment ZIP archive + - `src_w_deps.zip` +2. Ensure Python 3 (version 3.6 minimum) is installed. +3. Activate the virtual environment by running: + - `.\venv\Scripts\activate.bat` +4. Install dependencies: `pip install -r requirements.txt` +5. Run `python EmpDat.py` + +#### The hard way: from scratch +Most of the steps require a terminal window. +1. Acquire the source from the deployment ZIP archive + - `src.zip` 2. Ensure Python 3 (version 3.6 minimum) is installed. 3. Install dependencies: `pip3 install -r requirements.txt` 4. Run `python3 EmpDat.py` @@ -42,9 +50,10 @@ Most of the steps require a terminal window. - Password: `Ineed2changemypassword!` 2. On the top bar, click **File > Change Password** and change the super-admin's password to preference -3. Import previous database of employees +3. If desired, import previous database of employees - On the top bar, click **Import > Employees** and locate the previous CSV database - *Note: the database can be converted back into a CSV using the Employee Directory Report* +4. Consult the User Manual for performing other operations ## Development diff --git a/docs/index.md b/docs/index.md index 6ce258a..f77c408 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,5 @@ # EmpDat +Is this hard to read? [View this on GitHub!](https://uvu-cs2450-001-fall2020-team2.github.io/EmpDat/) Employee Database Application for CS 2450-001 Fall 2020. - Kevin Thorne @@ -20,16 +21,24 @@ However, EmpDat can be run on any operating system Python is supported on. ### The easy way ##### Windows: -1. ~~Run the installer wizard in win/EmpDatSetup.exe~~ - - Due to security issues with Windows, an installation wizard is not possible. -1. Unzip the `Portable.exe.zip` to a location of choice. +1. Unzip the `Portable.win.zip` to a location of choice. 2. Run `EmpDat.exe` 3. Done! -#### The hard way: from source +#### The harder way: from included environment Most of the steps require a terminal window. -1. Acquire the source from either the deployment ZIP archive - - `src/` is the source folder within the deployment ZIP archive +1. Acquire the source with dependencies from the deployment ZIP archive + - `src_w_deps.zip` +2. Ensure Python 3 (version 3.6 minimum) is installed. +3. Activate the virtual environment by running: + - `.\venv\Scripts\activate.bat` +4. Install dependencies: `pip install -r requirements.txt` +5. Run `python EmpDat.py` + +#### The hard way: from scratch +Most of the steps require a terminal window. +1. Acquire the source from the deployment ZIP archive + - `src.zip` 2. Ensure Python 3 (version 3.6 minimum) is installed. 3. Install dependencies: `pip3 install -r requirements.txt` 4. Run `python3 EmpDat.py` @@ -41,9 +50,10 @@ Most of the steps require a terminal window. - Password: `Ineed2changemypassword!` 2. On the top bar, click **File > Change Password** and change the super-admin's password to preference -3. Import previous database of employees +3. If desired, import previous database of employees - On the top bar, click **Import > Employees** and locate the previous CSV database - *Note: the database can be converted back into a CSV using the Employee Directory Report* +4. Consult the User Manual for performing other operations ## Development @@ -62,9 +72,8 @@ Any time anything needs to be ran or installed within the application, activate the virtual environment using Step 3 above. Deactivation can be done with the command `deactivate`. - ### Documentation ##### [ui/](ui/index.html) - Front end ##### [tests/](tests/index.html) - Tests ##### [lib/](lib/index.html) - Back end -##### [EmpDat.py](EmpDat.html) - Main +##### [EmpDat.py](EmpDat.html) - Main \ No newline at end of file diff --git a/docs/lib/cli/import_csv.html b/docs/lib/cli/import_csv.html index 08d3d34..5cbf16b 100644 --- a/docs/lib/cli/import_csv.html +++ b/docs/lib/cli/import_csv.html @@ -37,7 +37,7 @@

Module lib.cli.import_csv

from lib.model.employee import Employee from lib.model.receipt import Receipt -from lib.model.time_sheet import TimeSheet +from lib.model.timesheet import TimeSheet def _import_csv(filepath: str, has_headers=False): diff --git a/docs/lib/layer/security.html b/docs/lib/layer/security.html index 2c367f4..9f3b10d 100644 --- a/docs/lib/layer/security.html +++ b/docs/lib/layer/security.html @@ -41,6 +41,7 @@

Module lib.layer.security

CAN_UPDATE = 'can_update' # falsy. Base rule is to reject CAN_DESTROY = 'can_delete' # falsy. Base rule is to reject CAN_APPROVE = 'can_approve' # falsy. Base rule is to reject +CAN = 'can' ROLES = { "Admin": { @@ -68,20 +69,67 @@

Module lib.layer.security

"classification_id", "paymethod_id", "role" + ], + "timesheet": [ + "*" + ], + "receipt": [ + "*" ] }, + CAN_CREATE: [ + "timesheet", + "receipt" + ], + CAN: [ + "payroll", + ] }, "Reporter": { CANT_READ: { "employee": [ + "role", "social_security_number", - "role" + "address_line1", + "address_line2", + "city", + "state", + "zipcode", + "salary", + "hourly_rate", + "commission_rate", + "bank_routing", + "bank_account", + "paymethod_id", + "payment_method", # view model field of the above ] + }, + CAN_CREATE: [ + "timesheet", + "receipt" + ], + CAN_UPDATE: { + "timesheet": { + "*" + }, + "receipt": { + "*" + } } }, "Viewer": { CANT_READ: { "employee": [ + "id", + "role", + "social_security_number", + "start_date", + "date_of_birth", + "address_line1", + "address_line2", + "city", + "state", + "zipcode", "salary", "hourly_rate", "commission_rate", @@ -89,7 +137,10 @@

Module lib.layer.security

"bank_account", "classification_id", "paymethod_id", - "role" + "classification", # view model field of the above + "payment_method", # view model field of the above + 'date_left', + 'notes' ] } } @@ -153,7 +204,7 @@

Module lib.layer.security

'author_user_id': self.user.id, 'table_name': repo_cls.resource_uri, 'row_id': None, - 'changes': changes, + 'changes': ChangeRequest.serialize_dates(changes), 'reason': 'No reason given' }) request = ChangeRequest.create(request) @@ -228,6 +279,7 @@

Module lib.layer.security

old_model = repo_cls.read(getattr(updated_model, repo_cls.id_attr)) changes = list(dictdiffer.diff(old_model.to_dict(), updated_model.to_dict())) + # print(changes) for action, field, values in changes: if action == 'add': @@ -253,7 +305,7 @@

Module lib.layer.security

'author_user_id': self.user.id, 'table_name': repo_cls.resource_uri, 'row_id': getattr(updated_model, id_attr), - 'changes': changes, + 'changes': ChangeRequest.serialize_dates(changes), 'reason': 'No reason given' }) request = ChangeRequest.create(request) @@ -269,7 +321,63 @@

Module lib.layer.security

return if model_name not in self.user_role[CAN_DESTROY]: - raise SecurityException(f'Destroying {model_name} records is not allowed') + raise SecurityException(f'Destroying {model_name} records is not allowed') + + def can_create(self, resource_uri): + """ + Checks if user can create X + + :param resource_uri: X + :return: True if can create X + """ + if self.user_role and CAN_CREATE in self.user_role and '*' in self.user_role[CAN_UPDATE]: + return True + + return self.user_role \ + and CAN_CREATE in self.user_role \ + and resource_uri in self.user_role[CAN_CREATE] + + def can_read(self, resource_uri, field): + """ + Calculates if a role can access a certain field + + :param role: Role string + :param resource_uri: URI str + :param field: field name + :return: bool if can read + """ + + return not (self.user_role + and CANT_READ in self.user_role + and resource_uri in self.user_role[CANT_READ] + and field in self.user_role[CANT_READ][resource_uri]) + + def can_update(self, resource_uri): + """ + Checks if user can update X + + :param resource_uri: X + :return: True if can update X + """ + if self.user_role and CAN_UPDATE in self.user_role and '*' in self.user_role[CAN_UPDATE]: + return True + + return self.user_role \ + and CAN_UPDATE in self.user_role \ + and resource_uri in self.user_role[CAN_UPDATE] + + def can_(self, custom_operation: str): + """ + Checks if an arbitrary operation can be done with this role + + :param custom_operation: operation string + :return: bool if can do X + """ + if self.user_role_name == 'Admin': + return True + + return self.user_role and CAN in self.user_role \ + and custom_operation in self.user_role[CAN]
@@ -378,7 +486,7 @@

Ancestors

'author_user_id': self.user.id, 'table_name': repo_cls.resource_uri, 'row_id': None, - 'changes': changes, + 'changes': ChangeRequest.serialize_dates(changes), 'reason': 'No reason given' }) request = ChangeRequest.create(request) @@ -453,6 +561,7 @@

Ancestors

old_model = repo_cls.read(getattr(updated_model, repo_cls.id_attr)) changes = list(dictdiffer.diff(old_model.to_dict(), updated_model.to_dict())) + # print(changes) for action, field, values in changes: if action == 'add': @@ -478,7 +587,7 @@

Ancestors

'author_user_id': self.user.id, 'table_name': repo_cls.resource_uri, 'row_id': getattr(updated_model, id_attr), - 'changes': changes, + 'changes': ChangeRequest.serialize_dates(changes), 'reason': 'No reason given' }) request = ChangeRequest.create(request) @@ -494,7 +603,63 @@

Ancestors

return if model_name not in self.user_role[CAN_DESTROY]: - raise SecurityException(f'Destroying {model_name} records is not allowed') + raise SecurityException(f'Destroying {model_name} records is not allowed') + + def can_create(self, resource_uri): + """ + Checks if user can create X + + :param resource_uri: X + :return: True if can create X + """ + if self.user_role and CAN_CREATE in self.user_role and '*' in self.user_role[CAN_UPDATE]: + return True + + return self.user_role \ + and CAN_CREATE in self.user_role \ + and resource_uri in self.user_role[CAN_CREATE] + + def can_read(self, resource_uri, field): + """ + Calculates if a role can access a certain field + + :param role: Role string + :param resource_uri: URI str + :param field: field name + :return: bool if can read + """ + + return not (self.user_role + and CANT_READ in self.user_role + and resource_uri in self.user_role[CANT_READ] + and field in self.user_role[CANT_READ][resource_uri]) + + def can_update(self, resource_uri): + """ + Checks if user can update X + + :param resource_uri: X + :return: True if can update X + """ + if self.user_role and CAN_UPDATE in self.user_role and '*' in self.user_role[CAN_UPDATE]: + return True + + return self.user_role \ + and CAN_UPDATE in self.user_role \ + and resource_uri in self.user_role[CAN_UPDATE] + + def can_(self, custom_operation: str): + """ + Checks if an arbitrary operation can be done with this role + + :param custom_operation: operation string + :return: bool if can do X + """ + if self.user_role_name == 'Admin': + return True + + return self.user_role and CAN in self.user_role \ + and custom_operation in self.user_role[CAN]

Ancestors

Methods

+
+def can_(self, custom_operation: str) +
+
+

Checks if an arbitrary operation can be done with this role

+

:param custom_operation: operation string +:return: bool if can do X

+
+ +Expand source code + +
def can_(self, custom_operation: str):
+    """
+    Checks if an arbitrary operation can be done with this role
+
+    :param custom_operation: operation string
+    :return: bool if can do X
+    """
+    if self.user_role_name == 'Admin':
+        return True
+
+    return self.user_role and CAN in self.user_role \
+           and custom_operation in self.user_role[CAN]
+
+
+
+def can_create(self, resource_uri) +
+
+

Checks if user can create X

+

:param resource_uri: X +:return: True if can create X

+
+ +Expand source code + +
def can_create(self, resource_uri):
+    """
+    Checks if user can create X
+
+    :param resource_uri: X
+    :return: True if can create X
+    """
+    if self.user_role and CAN_CREATE in self.user_role and '*' in self.user_role[CAN_UPDATE]:
+        return True
+
+    return self.user_role \
+           and CAN_CREATE in self.user_role \
+           and resource_uri in self.user_role[CAN_CREATE]
+
+
+
+def can_read(self, resource_uri, field) +
+
+

Calculates if a role can access a certain field

+

:param role: Role string +:param resource_uri: URI str +:param field: field name +:return: bool if can read

+
+ +Expand source code + +
def can_read(self, resource_uri, field):
+    """
+    Calculates if a role can access a certain field
+
+    :param role: Role string
+    :param resource_uri: URI str
+    :param field: field name
+    :return: bool if can read
+    """
+
+    return not (self.user_role
+                and CANT_READ in self.user_role
+                and resource_uri in self.user_role[CANT_READ]
+                and field in self.user_role[CANT_READ][resource_uri])
+
+
+
+def can_update(self, resource_uri) +
+
+

Checks if user can update X

+

:param resource_uri: X +:return: True if can update X

+
+ +Expand source code + +
def can_update(self, resource_uri):
+    """
+    Checks if user can update X
+
+    :param resource_uri: X
+    :return: True if can update X
+    """
+    if self.user_role and CAN_UPDATE in self.user_role and '*' in self.user_role[CAN_UPDATE]:
+        return True
+
+    return self.user_role \
+           and CAN_UPDATE in self.user_role \
+           and resource_uri in self.user_role[CAN_UPDATE]
+
+
def on_update(self, repo_cls, updated_model, id_attr='id')
@@ -577,6 +848,7 @@

Methods

old_model = repo_cls.read(getattr(updated_model, repo_cls.id_attr)) changes = list(dictdiffer.diff(old_model.to_dict(), updated_model.to_dict())) + # print(changes) for action, field, values in changes: if action == 'add': @@ -602,7 +874,7 @@

Methods

'author_user_id': self.user.id, 'table_name': repo_cls.resource_uri, 'row_id': getattr(updated_model, id_attr), - 'changes': changes, + 'changes': ChangeRequest.serialize_dates(changes), 'reason': 'No reason given' }) request = ChangeRequest.create(request) @@ -647,6 +919,10 @@

SecurityLayer

diff --git a/docs/lib/model/change_request.html b/docs/lib/model/change_request.html index 583a086..00c6817 100644 --- a/docs/lib/model/change_request.html +++ b/docs/lib/model/change_request.html @@ -98,22 +98,27 @@

Module lib.model.change_request

if not model_cls: raise Exception("Table name given in the ChangeRequest does not exist!") + changes = ChangeRequest.deserialize_dates(self.changes) + if self.row_id: # is just a change? model = model_cls.read(self.row_id) - for diff_type, field, values in self.changes: + for diff_type, field, values in changes: if diff_type == 'change': setattr(model, field, values[1]) model_cls.update(model) else: # it must be an entire new object model_raw = {} - for diff_type, field, values in self.changes: + for diff_type, field, values in changes: if diff_type == 'add': - setattr(model_raw, field, values[1]) + for couple in values: + model_raw[couple[0]] = couple[1] model_cls.create(model_cls(model_raw)) self.approved_by_user_id = approved_by.id # pylint: disable=attribute-defined-outside-init self.approved_at = datetime.datetime.now() # pylint: disable=attribute-defined-outside-init + self.changes = ChangeRequest.serialize_dates(self.changes) + ChangeRequest.update(self) @classmethod @@ -131,11 +136,22 @@

Module lib.model.change_request

""" result = "" for diff_type, field, values in changes: # pylint: disable=unused-variable - if model_name: - model_cls = find_model_by_name(model_name) - result += f"{model_cls.view_columns[field]}: {values[0]} > {values[1]}\n" + if diff_type == 'change': + if model_name: + model_cls = find_model_by_name(model_name) + result += f"{model_cls.view_columns[field]}: {values[0]} => {values[1]}\n" + else: + result += f"{field}: {values[0]} > {values[1]}\n" else: - result += f"{field}: {values[0]} > {values[1]}\n" + for couple in values: + if model_name: + model_cls = find_model_by_name(model_name) + if couple[0] in model_cls.view_columns: + result += f"{model_cls.view_columns[couple[0]]}: {couple[1]}\n" + # else: + # result += f"{model_cls.view_columns[couple[0]]}: {couple[1]}\n" + else: + result += f"{couple[0]}: {couple[1]}\n" return result.rstrip() @classmethod @@ -152,7 +168,38 @@

Module lib.model.change_request

Column('modified_at', DateTime), Column('approved_by_user_id', BigInteger, nullable=True), extend_existing=True - ) + ) + + @staticmethod + def serialize_dates(changes): + for action, field, values in changes: + if action == 'add': + for i in range(len(values)): + couple = values[i] + mutable = list(couple) + if isinstance(couple[1], datetime.datetime): + mutable[1] = couple[1].isoformat() + values[i] = tuple(mutable) + + return changes + + @staticmethod + def deserialize_dates(changes): + for action, field, values in changes: + if action == 'add': + for i in range(len(values)): + couple = values[i] + mutable = list(couple) + try: + mutable[1] = datetime.datetime.strptime(couple[1], "%Y-%m-%dT%H:%M:%S") + values[i] = tuple(mutable) + except: + try: + mutable[1] = datetime.datetime.strptime(couple[1], "%Y-%m-%dT%H:%M:%S.%f") + values[i] = tuple(mutable) + except: + continue + return changes
@@ -230,22 +277,27 @@

Classes

if not model_cls: raise Exception("Table name given in the ChangeRequest does not exist!") + changes = ChangeRequest.deserialize_dates(self.changes) + if self.row_id: # is just a change? model = model_cls.read(self.row_id) - for diff_type, field, values in self.changes: + for diff_type, field, values in changes: if diff_type == 'change': setattr(model, field, values[1]) model_cls.update(model) else: # it must be an entire new object model_raw = {} - for diff_type, field, values in self.changes: + for diff_type, field, values in changes: if diff_type == 'add': - setattr(model_raw, field, values[1]) + for couple in values: + model_raw[couple[0]] = couple[1] model_cls.create(model_cls(model_raw)) self.approved_by_user_id = approved_by.id # pylint: disable=attribute-defined-outside-init self.approved_at = datetime.datetime.now() # pylint: disable=attribute-defined-outside-init + self.changes = ChangeRequest.serialize_dates(self.changes) + ChangeRequest.update(self) @classmethod @@ -263,11 +315,22 @@

Classes

""" result = "" for diff_type, field, values in changes: # pylint: disable=unused-variable - if model_name: - model_cls = find_model_by_name(model_name) - result += f"{model_cls.view_columns[field]}: {values[0]} > {values[1]}\n" + if diff_type == 'change': + if model_name: + model_cls = find_model_by_name(model_name) + result += f"{model_cls.view_columns[field]}: {values[0]} => {values[1]}\n" + else: + result += f"{field}: {values[0]} > {values[1]}\n" else: - result += f"{field}: {values[0]} > {values[1]}\n" + for couple in values: + if model_name: + model_cls = find_model_by_name(model_name) + if couple[0] in model_cls.view_columns: + result += f"{model_cls.view_columns[couple[0]]}: {couple[1]}\n" + # else: + # result += f"{model_cls.view_columns[couple[0]]}: {couple[1]}\n" + else: + result += f"{couple[0]}: {couple[1]}\n" return result.rstrip() @classmethod @@ -284,7 +347,38 @@

Classes

Column('modified_at', DateTime), Column('approved_by_user_id', BigInteger, nullable=True), extend_existing=True - ) + ) + + @staticmethod + def serialize_dates(changes): + for action, field, values in changes: + if action == 'add': + for i in range(len(values)): + couple = values[i] + mutable = list(couple) + if isinstance(couple[1], datetime.datetime): + mutable[1] = couple[1].isoformat() + values[i] = tuple(mutable) + + return changes + + @staticmethod + def deserialize_dates(changes): + for action, field, values in changes: + if action == 'add': + for i in range(len(values)): + couple = values[i] + mutable = list(couple) + try: + mutable[1] = datetime.datetime.strptime(couple[1], "%Y-%m-%dT%H:%M:%S") + values[i] = tuple(mutable) + except: + try: + mutable[1] = datetime.datetime.strptime(couple[1], "%Y-%m-%dT%H:%M:%S.%f") + values[i] = tuple(mutable) + except: + continue + return changes

Ancestors

Class variables

@@ -633,15 +636,19 @@

Example

""" super().__init__(data) - def to_view_model(self): + def to_view_model(self, security_layer=None): """ Uses the view_columns dictionary and converts the model instance into a dictionary ready for presentation + :param security_layer: SecurityLayer instance :return: view model dictionary """ view_model = {} for key, value in self.view_columns.items(): + if security_layer: + if not security_layer.can_read(self.resource_uri, key): + continue if getattr(self, key): view_model[value] = getattr(self, key) else: @@ -672,6 +679,7 @@

Subclasses

Class variables

@@ -713,25 +721,30 @@

Static methods

Methods

-def to_view_model(self) +def to_view_model(self, security_layer=None)

Uses the view_columns dictionary and converts the model instance into a dictionary ready for presentation

-

:return: view model dictionary

+

:param security_layer: SecurityLayer instance +:return: view model dictionary

Expand source code -
def to_view_model(self):
+
def to_view_model(self, security_layer=None):
     """
     Uses the view_columns dictionary and converts the model
     instance into a dictionary ready for presentation
 
+    :param security_layer: SecurityLayer instance
     :return: view model dictionary
     """
     view_model = {}
     for key, value in self.view_columns.items():
+        if security_layer:
+            if not security_layer.can_read(self.resource_uri, key):
+                continue
         if getattr(self, key):
             view_model[value] = getattr(self, key)
         else:
@@ -814,7 +827,7 @@ 

Subclasses

  • Department
  • Employee
  • Receipt
  • -
  • TimeSheet
  • +
  • TimeSheet
  • Methods

    @@ -900,7 +913,7 @@

    Index

  • lib.model.department
  • lib.model.employee
  • lib.model.receipt
  • -
  • lib.model.time_sheet
  • +
  • lib.model.timesheet
  • Functions

    diff --git a/docs/lib/model/receipt.html b/docs/lib/model/receipt.html index b48a98a..c2a7f68 100644 --- a/docs/lib/model/receipt.html +++ b/docs/lib/model/receipt.html @@ -52,6 +52,14 @@

    Module lib.model.receipt

    field_casts = { } + view_columns = { + 'id': 'ID', + 'user_id': 'Owner ID', + 'user': 'Owner', + 'amount': 'Amount', + 'datetime_end': 'Time Out', + 'paid': 'Is Paid?', + } def __init__(self, data): DynamicModel.__init__(self, data) @@ -116,6 +124,14 @@

    Classes

    field_casts = { } + view_columns = { + 'id': 'ID', + 'user_id': 'Owner ID', + 'user': 'Owner', + 'amount': 'Amount', + 'datetime_end': 'Time Out', + 'paid': 'Is Paid?', + } def __init__(self, data): DynamicModel.__init__(self, data) @@ -169,6 +185,10 @@

    Class variables

    +
    var view_columns
    +
    +
    +
  • Inherited members

    diff --git a/docs/lib/repository/db.html b/docs/lib/repository/db.html index 34e15f0..e512ee4 100644 --- a/docs/lib/repository/db.html +++ b/docs/lib/repository/db.html @@ -677,7 +677,7 @@

    Subclasses

  • Department
  • Employee
  • Receipt
  • -
  • TimeSheet
  • +
  • TimeSheet
  • Class variables

    diff --git a/docs/lib/utils.html b/docs/lib/utils.html index 70b8a6d..8c1925d 100644 --- a/docs/lib/utils.html +++ b/docs/lib/utils.html @@ -56,7 +56,10 @@

    Module lib.utils

    if isinstance(date_thing, datetime.date): return date_thing if isinstance(date_thing, str): - return datetime.datetime.strptime(date_thing, '%Y-%m-%d').date() + try: + return datetime.datetime.strptime(date_thing, '%m/%d/%y').date() + except: + return datetime.datetime.strptime(date_thing, '%Y-%m-%d').date() raise ValueError(f'Invalid date "{date_thing}" given')
    @@ -89,7 +92,10 @@

    Functions

    if isinstance(date_thing, datetime.date): return date_thing if isinstance(date_thing, str): - return datetime.datetime.strptime(date_thing, '%Y-%m-%d').date() + try: + return datetime.datetime.strptime(date_thing, '%m/%d/%y').date() + except: + return datetime.datetime.strptime(date_thing, '%Y-%m-%d').date() raise ValueError(f'Invalid date "{date_thing}" given')
    diff --git a/docs/ui/control/change_requests.html b/docs/ui/control/change_requests.html index 89fd114..329cbf2 100644 --- a/docs/ui/control/change_requests.html +++ b/docs/ui/control/change_requests.html @@ -56,7 +56,9 @@

    Module ui.control.change_requests

    Loads all change requests and has the view display them :return: None """ - change_requests = ChangeRequest.read_all() + change_requests = ChangeRequest.read_by(filters={ + 'approved_at': None + }) i = 0 for request in change_requests: i += 1 @@ -69,6 +71,7 @@

    Module ui.control.change_requests

    Wipes and refreshes change requests view :return: None """ + self.view.destroy_results() self.load() self.view.table.redraw() @@ -85,7 +88,7 @@

    Module ui.control.change_requests

    ids = self.view.table.get_selectedRecordNames() for request_id in ids: request = ChangeRequest.read(request_id) - request.apply_to_db(store.AUTHENTICATED_USER) + request.apply_to_db(store.SECURITY_LAYER.user) self.view.show_info('Approvals Successful', 'Changes applied successfully!') self.refresh() @@ -139,7 +142,9 @@

    Classes

    Loads all change requests and has the view display them :return: None """ - change_requests = ChangeRequest.read_all() + change_requests = ChangeRequest.read_by(filters={ + 'approved_at': None + }) i = 0 for request in change_requests: i += 1 @@ -152,6 +157,7 @@

    Classes

    Wipes and refreshes change requests view :return: None """ + self.view.destroy_results() self.load() self.view.table.redraw() @@ -168,7 +174,7 @@

    Classes

    ids = self.view.table.get_selectedRecordNames() for request_id in ids: request = ChangeRequest.read(request_id) - request.apply_to_db(store.AUTHENTICATED_USER) + request.apply_to_db(store.SECURITY_LAYER.user) self.view.show_info('Approvals Successful', 'Changes applied successfully!') self.refresh() @@ -207,7 +213,7 @@

    Methods

    ids = self.view.table.get_selectedRecordNames() for request_id in ids: request = ChangeRequest.read(request_id) - request.apply_to_db(store.AUTHENTICATED_USER) + request.apply_to_db(store.SECURITY_LAYER.user) self.view.show_info('Approvals Successful', 'Changes applied successfully!') self.refresh()
    @@ -227,7 +233,9 @@

    Methods

    Loads all change requests and has the view display them :return: None """ - change_requests = ChangeRequest.read_all() + change_requests = ChangeRequest.read_by(filters={ + 'approved_at': None + }) i = 0 for request in change_requests: i += 1 @@ -251,6 +259,7 @@

    Methods

    Wipes and refreshes change requests view :return: None """ + self.view.destroy_results() self.load() self.view.table.redraw() diff --git a/docs/ui/control/database.html b/docs/ui/control/database.html index be02203..be6a74a 100644 --- a/docs/ui/control/database.html +++ b/docs/ui/control/database.html @@ -40,7 +40,7 @@

    Module ui.control.database

    from lib.layer.security import ChangeRequestException, SecurityException from lib.model.employee import Employee from lib.model.receipt import Receipt -from lib.model.time_sheet import TimeSheet +from lib.model.timesheet import TimeSheet from lib.repository.validator import is_valid_against, ValidationException from lib.utils import sha_hash from ui import store @@ -127,7 +127,8 @@

    Module ui.control.database

    i = 0 for employee in employees: i += 1 - self.view.add_to_result(employee.id, employee.to_view_model()) + self.view.add_to_result(employee.id, + employee.to_view_model(security_layer=store.SECURITY_LAYER)) self.view.table.autoResizeColumns() @@ -191,9 +192,9 @@

    Module ui.control.database

    Employee.update(employee) except ChangeRequestException: change_request_submitted = True - except SecurityException: + except SecurityException as error: self.view.highlight_invalid_rows([employee_id]) - self.view.show_error('Error', 'Access Denied') + self.view.show_error('Error', f'Access Denied\n{error}') return except ValidationException as error: self.view.highlight_invalid_cell(employee_id, @@ -246,7 +247,7 @@

    Module ui.control.database

    """ def on_save(dialog, old_pass: str, password: str, password_confirm: str): - _on_password_save(self.view, dialog, store.AUTHENTICATED_USER.id, + _on_password_save(self.view, dialog, store.SECURITY_LAYER.user.id, old_pass, password, password_confirm) MyPasswordDialog({ @@ -507,7 +508,8 @@

    Classes

    i = 0 for employee in employees: i += 1 - self.view.add_to_result(employee.id, employee.to_view_model()) + self.view.add_to_result(employee.id, + employee.to_view_model(security_layer=store.SECURITY_LAYER)) self.view.table.autoResizeColumns() @@ -571,9 +573,9 @@

    Classes

    Employee.update(employee) except ChangeRequestException: change_request_submitted = True - except SecurityException: + except SecurityException as error: self.view.highlight_invalid_rows([employee_id]) - self.view.show_error('Error', 'Access Denied') + self.view.show_error('Error', f'Access Denied\n{error}') return except ValidationException as error: self.view.highlight_invalid_cell(employee_id, @@ -626,7 +628,7 @@

    Classes

    """ def on_save(dialog, old_pass: str, password: str, password_confirm: str): - _on_password_save(self.view, dialog, store.AUTHENTICATED_USER.id, + _on_password_save(self.view, dialog, store.SECURITY_LAYER.user.id, old_pass, password, password_confirm) MyPasswordDialog({ @@ -868,7 +870,7 @@

    Methods

    """ def on_save(dialog, old_pass: str, password: str, password_confirm: str): - _on_password_save(self.view, dialog, store.AUTHENTICATED_USER.id, + _on_password_save(self.view, dialog, store.SECURITY_LAYER.user.id, old_pass, password, password_confirm) MyPasswordDialog({ @@ -1093,7 +1095,8 @@

    Methods

    i = 0 for employee in employees: i += 1 - self.view.add_to_result(employee.id, employee.to_view_model()) + self.view.add_to_result(employee.id, + employee.to_view_model(security_layer=store.SECURITY_LAYER)) self.view.table.autoResizeColumns() @@ -1319,9 +1322,9 @@

    Methods

    Employee.update(employee) except ChangeRequestException: change_request_submitted = True - except SecurityException: + except SecurityException as error: self.view.highlight_invalid_rows([employee_id]) - self.view.show_error('Error', 'Access Denied') + self.view.show_error('Error', f'Access Denied\n{error}') return except ValidationException as error: self.view.highlight_invalid_cell(employee_id, diff --git a/docs/ui/control/login.html b/docs/ui/control/login.html index b5c5fb3..f808585 100644 --- a/docs/ui/control/login.html +++ b/docs/ui/control/login.html @@ -72,8 +72,7 @@

    Module ui.control.login

    authenticated = Employee.authenticate(username, password) if authenticated is not None: - store.AUTHENTICATED_USER = authenticated - SecurityLayer(authenticated) + store.SECURITY_LAYER = SecurityLayer(authenticated) print("Logged in as user ID", authenticated.id) self.view.destroy() DatabaseController().show() @@ -133,8 +132,7 @@

    Classes

    authenticated = Employee.authenticate(username, password) if authenticated is not None: - store.AUTHENTICATED_USER = authenticated - SecurityLayer(authenticated) + store.SECURITY_LAYER = SecurityLayer(authenticated) print("Logged in as user ID", authenticated.id) self.view.destroy() DatabaseController().show() @@ -179,8 +177,7 @@

    Methods

    authenticated = Employee.authenticate(username, password) if authenticated is not None: - store.AUTHENTICATED_USER = authenticated - SecurityLayer(authenticated) + store.SECURITY_LAYER = SecurityLayer(authenticated) print("Logged in as user ID", authenticated.id) self.view.destroy() DatabaseController().show() diff --git a/docs/ui/store/index.html b/docs/ui/store/index.html index b9682d4..56b5f8e 100644 --- a/docs/ui/store/index.html +++ b/docs/ui/store/index.html @@ -30,7 +30,7 @@

    Module ui.store

    """
     Globals for the UI session. This should be turned into a singleton object later, but this suited
     """
    -AUTHENTICATED_USER = None
    +SECURITY_LAYER = None
    diff --git a/docs/ui/window/change_requests.html b/docs/ui/window/change_requests.html index 03a52a8..8a5f794 100644 --- a/docs/ui/window/change_requests.html +++ b/docs/ui/window/change_requests.html @@ -50,7 +50,13 @@

    Module ui.window.change_requests

    loaded = json.loads(raw) table_name = loaded[0] row_id = loaded[1] - return find_model_by_name(table_name).read(row_id).get_name() + + if row_id: + try: + return find_model_by_name(table_name).read(row_id).get_name() + except AttributeError: + pass + return 'NEW' class ChangeRequestsWindow(TkinterWindow): @@ -73,16 +79,16 @@

    Module ui.window.change_requests

    frame.pack(fill=BOTH, expand=1) self.table = EmpDatTableCanvas(frame, col_modifiers={ - 2: { + 'ID affected': { 'render_as': lambda x: _render_row_id(x) # pylint: disable=unnecessary-lambda }, - 3: { + 'Changes': { 'render_as': lambda x: ChangeRequest.prettify_changes(*json.loads(x)) } }, - on_selected=lambda x: self.set_bottom_state('normal'), - data=self.results, rowheight=50) + on_selected=lambda: self.set_bottom_state('normal'), + data=self.results, rowheight=150) self.table.show() self.table.read_only = True @@ -131,7 +137,16 @@

    Module ui.window.change_requests

    """ to_add['ID affected'] = json.dumps((to_add['Data Type'], to_add['ID affected'])) to_add['Changes'] = json.dumps((to_add['Changes'], to_add['Data Type'])) - self.table.addRow(record_id, **to_add) + self.table.addRow(record_id, **to_add) + + def destroy_results(self): + """ + Destroy all rows + :return: None + """ + keys = list(self.table.model.data.keys()) + for key in keys: + self.table.model.deleteRow(key=key)
    @@ -175,16 +190,16 @@

    Classes

    frame.pack(fill=BOTH, expand=1) self.table = EmpDatTableCanvas(frame, col_modifiers={ - 2: { + 'ID affected': { 'render_as': lambda x: _render_row_id(x) # pylint: disable=unnecessary-lambda }, - 3: { + 'Changes': { 'render_as': lambda x: ChangeRequest.prettify_changes(*json.loads(x)) } }, - on_selected=lambda x: self.set_bottom_state('normal'), - data=self.results, rowheight=50) + on_selected=lambda: self.set_bottom_state('normal'), + data=self.results, rowheight=150) self.table.show() self.table.read_only = True @@ -233,7 +248,16 @@

    Classes

    """ to_add['ID affected'] = json.dumps((to_add['Data Type'], to_add['ID affected'])) to_add['Changes'] = json.dumps((to_add['Changes'], to_add['Data Type'])) - self.table.addRow(record_id, **to_add) + self.table.addRow(record_id, **to_add) + + def destroy_results(self): + """ + Destroy all rows + :return: None + """ + keys = list(self.table.model.data.keys()) + for key in keys: + self.table.model.deleteRow(key=key)

    Ancestors

    diff --git a/docs/ui/window/database.html b/docs/ui/window/database.html index d01138a..7c4fc86 100644 --- a/docs/ui/window/database.html +++ b/docs/ui/window/database.html @@ -142,7 +142,7 @@

    Module ui.window.database

    data=self.results, rowheight=50) self.table.show() - if store.AUTHENTICATED_USER.role == 'Viewer': + if store.SECURITY_LAYER.user.role == 'Viewer': self.table.read_only = True self.create_menu() @@ -161,15 +161,17 @@

    Module ui.window.database

    self.filemenu = Menu(self.menubar, tearoff=False) # New Employee # adds a command to the menu option, calling it exit - self.filemenu.add_command(label="New Employee", - command=self.event_handlers['new_employee']) - self.filemenu.add_command(label="New Receipt", - command=self.event_handlers['new_receipt']) - self.filemenu.add_command(label="New Timesheet", - command=self.event_handlers['new_timesheet']) + if store.SECURITY_LAYER.can_create('employee'): + self.filemenu.add_command(label="New Employee", + command=self.event_handlers['new_employee']) + if store.SECURITY_LAYER.can_create('receipt'): + self.filemenu.add_command(label="New Receipt", + command=self.event_handlers['new_receipt']) + if store.SECURITY_LAYER.can_create('timesheet'): + self.filemenu.add_command(label="New Timesheet", + command=self.event_handlers['new_timesheet']) self.filemenu.add_separator() - if store.AUTHENTICATED_USER.role == 'Admin' \ - or store.AUTHENTICATED_USER.role == 'Accounting': + if store.SECURITY_LAYER.can_('payroll'): self.filemenu.add_command(label="Run Payroll", command=self.event_handlers['run_payroll']) self.filemenu.add_command(label="Change My Password", @@ -198,11 +200,11 @@

    Module ui.window.database

    command=self.event_handlers['import>timesheets']) self.menubar.add_cascade(label="Import", menu=self.import_menu) # Admin tab - if store.AUTHENTICATED_USER.role == 'Admin': + if store.SECURITY_LAYER.user.role == 'Admin': self.admin_menu = Menu(self.menubar, tearoff=False) self.admin_menu.add_command(label="Review Change Requests", command=self.event_handlers['admin>review']) - self.admin_menu.add_command(label="Change Passwords", + self.admin_menu.add_command(label="Set Passwords", command=self.event_handlers['admin>change_password']) self.menubar.add_cascade(label="Admin", menu=self.admin_menu) @@ -248,11 +250,12 @@

    Module ui.window.database

    state="disabled" ) - self.new_button.pack(side=LEFT, anchor=W) + if store.SECURITY_LAYER.can_create('employee'): + self.new_button.pack(side=LEFT, anchor=W) self.refresh_button.pack(side=LEFT, anchor=W) Label(buttons, - text=f"({store.AUTHENTICATED_USER.first_name} " - f"{store.AUTHENTICATED_USER.last_name})") \ + text=f"({store.SECURITY_LAYER.user.first_name} " + f"{store.SECURITY_LAYER.user.last_name})") \ .pack(side=LEFT, anchor=W) self.status = Label(buttons, text='') @@ -260,8 +263,9 @@

    Module ui.window.database

    Frame(buttons, relief='flat', borderwidth=0).pack(fill=X, expand=1) - self.save_button.pack(side=RIGHT, anchor=E) - self.delete_button.pack(side=RIGHT, anchor=E) + if store.SECURITY_LAYER.can_update('employee'): + self.save_button.pack(side=RIGHT, anchor=E) + self.delete_button.pack(side=RIGHT, anchor=E) self.search_button.pack(side=RIGHT, anchor=E) buttons.pack(side=RIGHT, fill=X, expand=1) @@ -521,7 +525,7 @@

    Classes

    data=self.results, rowheight=50) self.table.show() - if store.AUTHENTICATED_USER.role == 'Viewer': + if store.SECURITY_LAYER.user.role == 'Viewer': self.table.read_only = True self.create_menu() @@ -540,15 +544,17 @@

    Classes

    self.filemenu = Menu(self.menubar, tearoff=False) # New Employee # adds a command to the menu option, calling it exit - self.filemenu.add_command(label="New Employee", - command=self.event_handlers['new_employee']) - self.filemenu.add_command(label="New Receipt", - command=self.event_handlers['new_receipt']) - self.filemenu.add_command(label="New Timesheet", - command=self.event_handlers['new_timesheet']) + if store.SECURITY_LAYER.can_create('employee'): + self.filemenu.add_command(label="New Employee", + command=self.event_handlers['new_employee']) + if store.SECURITY_LAYER.can_create('receipt'): + self.filemenu.add_command(label="New Receipt", + command=self.event_handlers['new_receipt']) + if store.SECURITY_LAYER.can_create('timesheet'): + self.filemenu.add_command(label="New Timesheet", + command=self.event_handlers['new_timesheet']) self.filemenu.add_separator() - if store.AUTHENTICATED_USER.role == 'Admin' \ - or store.AUTHENTICATED_USER.role == 'Accounting': + if store.SECURITY_LAYER.can_('payroll'): self.filemenu.add_command(label="Run Payroll", command=self.event_handlers['run_payroll']) self.filemenu.add_command(label="Change My Password", @@ -577,11 +583,11 @@

    Classes

    command=self.event_handlers['import>timesheets']) self.menubar.add_cascade(label="Import", menu=self.import_menu) # Admin tab - if store.AUTHENTICATED_USER.role == 'Admin': + if store.SECURITY_LAYER.user.role == 'Admin': self.admin_menu = Menu(self.menubar, tearoff=False) self.admin_menu.add_command(label="Review Change Requests", command=self.event_handlers['admin>review']) - self.admin_menu.add_command(label="Change Passwords", + self.admin_menu.add_command(label="Set Passwords", command=self.event_handlers['admin>change_password']) self.menubar.add_cascade(label="Admin", menu=self.admin_menu) @@ -627,11 +633,12 @@

    Classes

    state="disabled" ) - self.new_button.pack(side=LEFT, anchor=W) + if store.SECURITY_LAYER.can_create('employee'): + self.new_button.pack(side=LEFT, anchor=W) self.refresh_button.pack(side=LEFT, anchor=W) Label(buttons, - text=f"({store.AUTHENTICATED_USER.first_name} " - f"{store.AUTHENTICATED_USER.last_name})") \ + text=f"({store.SECURITY_LAYER.user.first_name} " + f"{store.SECURITY_LAYER.user.last_name})") \ .pack(side=LEFT, anchor=W) self.status = Label(buttons, text='') @@ -639,8 +646,9 @@

    Classes

    Frame(buttons, relief='flat', borderwidth=0).pack(fill=X, expand=1) - self.save_button.pack(side=RIGHT, anchor=E) - self.delete_button.pack(side=RIGHT, anchor=E) + if store.SECURITY_LAYER.can_update('employee'): + self.save_button.pack(side=RIGHT, anchor=E) + self.delete_button.pack(side=RIGHT, anchor=E) self.search_button.pack(side=RIGHT, anchor=E) buttons.pack(side=RIGHT, fill=X, expand=1) @@ -826,11 +834,12 @@

    Methods

    state="disabled" ) - self.new_button.pack(side=LEFT, anchor=W) + if store.SECURITY_LAYER.can_create('employee'): + self.new_button.pack(side=LEFT, anchor=W) self.refresh_button.pack(side=LEFT, anchor=W) Label(buttons, - text=f"({store.AUTHENTICATED_USER.first_name} " - f"{store.AUTHENTICATED_USER.last_name})") \ + text=f"({store.SECURITY_LAYER.user.first_name} " + f"{store.SECURITY_LAYER.user.last_name})") \ .pack(side=LEFT, anchor=W) self.status = Label(buttons, text='') @@ -838,8 +847,9 @@

    Methods

    Frame(buttons, relief='flat', borderwidth=0).pack(fill=X, expand=1) - self.save_button.pack(side=RIGHT, anchor=E) - self.delete_button.pack(side=RIGHT, anchor=E) + if store.SECURITY_LAYER.can_update('employee'): + self.save_button.pack(side=RIGHT, anchor=E) + self.delete_button.pack(side=RIGHT, anchor=E) self.search_button.pack(side=RIGHT, anchor=E) buttons.pack(side=RIGHT, fill=X, expand=1)
    @@ -868,15 +878,17 @@

    Methods

    self.filemenu = Menu(self.menubar, tearoff=False) # New Employee # adds a command to the menu option, calling it exit - self.filemenu.add_command(label="New Employee", - command=self.event_handlers['new_employee']) - self.filemenu.add_command(label="New Receipt", - command=self.event_handlers['new_receipt']) - self.filemenu.add_command(label="New Timesheet", - command=self.event_handlers['new_timesheet']) + if store.SECURITY_LAYER.can_create('employee'): + self.filemenu.add_command(label="New Employee", + command=self.event_handlers['new_employee']) + if store.SECURITY_LAYER.can_create('receipt'): + self.filemenu.add_command(label="New Receipt", + command=self.event_handlers['new_receipt']) + if store.SECURITY_LAYER.can_create('timesheet'): + self.filemenu.add_command(label="New Timesheet", + command=self.event_handlers['new_timesheet']) self.filemenu.add_separator() - if store.AUTHENTICATED_USER.role == 'Admin' \ - or store.AUTHENTICATED_USER.role == 'Accounting': + if store.SECURITY_LAYER.can_('payroll'): self.filemenu.add_command(label="Run Payroll", command=self.event_handlers['run_payroll']) self.filemenu.add_command(label="Change My Password", @@ -905,11 +917,11 @@

    Methods

    command=self.event_handlers['import>timesheets']) self.menubar.add_cascade(label="Import", menu=self.import_menu) # Admin tab - if store.AUTHENTICATED_USER.role == 'Admin': + if store.SECURITY_LAYER.user.role == 'Admin': self.admin_menu = Menu(self.menubar, tearoff=False) self.admin_menu.add_command(label="Review Change Requests", command=self.event_handlers['admin>review']) - self.admin_menu.add_command(label="Change Passwords", + self.admin_menu.add_command(label="Set Passwords", command=self.event_handlers['admin>change_password']) self.menubar.add_cascade(label="Admin", menu=self.admin_menu) diff --git a/lib/layer/security.py b/lib/layer/security.py index 2f84ace..e1ebf01 100644 --- a/lib/layer/security.py +++ b/lib/layer/security.py @@ -250,7 +250,7 @@ def on_update(self, repo_cls, updated_model, id_attr='id'): # pylint: disable=t old_model = repo_cls.read(getattr(updated_model, repo_cls.id_attr)) changes = list(dictdiffer.diff(old_model.to_dict(), updated_model.to_dict())) - print(changes) + # print(changes) for action, field, values in changes: if action == 'add': diff --git a/lib/model/__init__.py b/lib/model/__init__.py index d6a63dd..7e84f6b 100644 --- a/lib/model/__init__.py +++ b/lib/model/__init__.py @@ -168,7 +168,6 @@ def to_view_model(self, security_layer=None): view_model = {} for key, value in self.view_columns.items(): if security_layer: - print(key) if not security_layer.can_read(self.resource_uri, key): continue if getattr(self, key): diff --git a/lib/model/change_request.py b/lib/model/change_request.py index 08bbf4e..e20e26b 100644 --- a/lib/model/change_request.py +++ b/lib/model/change_request.py @@ -143,7 +143,6 @@ def table(cls, metadata=MetaData()) -> Table: @staticmethod def serialize_dates(changes): - print(changes) for action, field, values in changes: if action == 'add': for i in range(len(values)): @@ -157,7 +156,6 @@ def serialize_dates(changes): @staticmethod def deserialize_dates(changes): - print(changes) for action, field, values in changes: if action == 'add': for i in range(len(values)): diff --git a/lib/model/receipt.py b/lib/model/receipt.py index d439329..0910a87 100644 --- a/lib/model/receipt.py +++ b/lib/model/receipt.py @@ -23,6 +23,14 @@ class Receipt(DatabaseRepository, DynamicModel, HasRelationships): field_casts = { } + view_columns = { + 'id': 'ID', + 'user_id': 'Owner ID', + 'user': 'Owner', + 'amount': 'Amount', + 'datetime_end': 'Time Out', + 'paid': 'Is Paid?', + } def __init__(self, data): DynamicModel.__init__(self, data) diff --git a/lib/model/timesheet.py b/lib/model/timesheet.py index 28e557d..23e6fce 100644 --- a/lib/model/timesheet.py +++ b/lib/model/timesheet.py @@ -28,7 +28,7 @@ class TimeSheet(DatabaseRepository, DynamicViewModel, HasRelationships): 'user': 'Owner', 'datetime_begin': 'Time In', 'datetime_end': 'Time Out', - 'paid': 'Time In', + 'paid': 'Is Paid?', } def __init__(self, data): diff --git a/ui/control/database.py b/ui/control/database.py index 3e08d54..3c395f6 100644 --- a/ui/control/database.py +++ b/ui/control/database.py @@ -156,9 +156,7 @@ def save(self): is_new = True try: - print(view_model) employee = Employee.from_view_model(view_model) - print(employee.to_dict()) if is_new: Employee.create(employee) else: diff --git a/ui/window/database.py b/ui/window/database.py index 4b344b8..613f5d4 100644 --- a/ui/window/database.py +++ b/ui/window/database.py @@ -328,7 +328,6 @@ def add_to_result(self, record_id, to_add: dict): :return: None """ self.table.addRow(record_id, **to_add) - print(to_add) def destroy_results(self): """