django-ledger 0.6.0.1__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-ledger might be problematic. Click here for more details.

Files changed (30) hide show
  1. assets/node_modules/node-gyp/update-gyp.py +0 -0
  2. django_ledger/__init__.py +1 -1
  3. django_ledger/admin/ledger.py +3 -1
  4. django_ledger/io/__init__.py +1 -3
  5. django_ledger/io/{io_digest.py → io_context.py} +8 -0
  6. django_ledger/io/io_core.py +19 -6
  7. django_ledger/io/io_library.py +79 -30
  8. django_ledger/io/roles.py +80 -86
  9. django_ledger/migrations/0016_remove_accountmodel_django_ledg_coa_mod_e19964_idx_and_more.py +44 -0
  10. django_ledger/models/accounts.py +73 -43
  11. django_ledger/models/bill.py +2 -0
  12. django_ledger/models/coa.py +2 -0
  13. django_ledger/models/entity.py +5 -3
  14. django_ledger/models/invoice.py +2 -1
  15. django_ledger/models/items.py +4 -2
  16. django_ledger/models/ledger.py +24 -12
  17. django_ledger/models/mixins.py +11 -7
  18. django_ledger/models/transactions.py +4 -35
  19. django_ledger/settings.py +1 -0
  20. django_ledger/urls/entity.py +1 -1
  21. django_ledger/urls/unit.py +1 -1
  22. django_ledger/views/entity.py +18 -12
  23. django_ledger/views/ledger.py +0 -1
  24. django_ledger/views/mixins.py +60 -30
  25. {django_ledger-0.6.0.1.dist-info → django_ledger-0.6.1.dist-info}/METADATA +10 -8
  26. {django_ledger-0.6.0.1.dist-info → django_ledger-0.6.1.dist-info}/RECORD +29 -28
  27. {django_ledger-0.6.0.1.dist-info → django_ledger-0.6.1.dist-info}/top_level.txt +1 -0
  28. {django_ledger-0.6.0.1.dist-info → django_ledger-0.6.1.dist-info}/AUTHORS.md +0 -0
  29. {django_ledger-0.6.0.1.dist-info → django_ledger-0.6.1.dist-info}/LICENSE +0 -0
  30. {django_ledger-0.6.0.1.dist-info → django_ledger-0.6.1.dist-info}/WHEEL +0 -0
@@ -87,6 +87,26 @@ class AccountModelQuerySet(MP_NodeQuerySet):
87
87
  """
88
88
  return self.filter(active=False)
89
89
 
90
+ def locked(self):
91
+ """
92
+ Filter locked elements.
93
+
94
+ This method filters the elements based on the `locked` attribute and returns a filtered queryset.
95
+
96
+ Returns:
97
+ A filtered queryset containing the locked elements.
98
+ """
99
+ return self.filter(locked=True)
100
+
101
+ def unlocked(self):
102
+ """
103
+ Returns a filtered version of an object, excluding any locked items.
104
+
105
+ Returns:
106
+ A filtered version of the object, excluding any locked items.
107
+ """
108
+ return self.filter(locked=False)
109
+
90
110
  def with_roles(self, roles: Union[List, str]):
91
111
  """
92
112
  This method is used to make query of accounts with a certain role. For instance, the fixed assets like
@@ -110,12 +130,25 @@ class AccountModelQuerySet(MP_NodeQuerySet):
110
130
  return self.filter(role__in=roles)
111
131
 
112
132
  def expenses(self):
133
+ """
134
+ Return the expenses filtered by the roles specified in GROUP_EXPENSES.
135
+
136
+ Returns:
137
+ QuerySet: A queryset containing the expenses filtered by the GROUP_EXPENSES roles..
138
+ """
113
139
  return self.filter(role__in=GROUP_EXPENSES)
114
140
 
115
141
  def is_coa_root(self):
142
+ """
143
+ Check if the account model instance is the Chart of Account Root.
144
+
145
+ Returns:
146
+ bool: True if the Account is the CoA Root, False otherwise.
147
+ """
116
148
  return self.filter(role__in=ROOT_GROUP)
117
149
 
118
150
  def not_coa_root(self):
151
+
119
152
  return self.exclude(role__in=ROOT_GROUP)
120
153
 
121
154
  def for_entity(self, entity_slug, user_model):
@@ -146,12 +179,16 @@ class AccountModelQuerySet(MP_NodeQuerySet):
146
179
  def is_role_default(self):
147
180
  return self.not_coa_root().filter(role_default=True)
148
181
 
182
+ def can_transact(self):
183
+ return self.filter(
184
+ Q(locked=False) & Q(active=True)
185
+ )
186
+
149
187
 
150
188
  class AccountModelManager(MP_NodeManager):
151
189
  """
152
- This Model Manager will be used as interface through which the database query operations can be provided to the
153
- Account Model. It uses the custom defined AccountModelQuerySet and hence overrides the normal get_queryset
154
- function which return all rows of a model.
190
+ AccountModelManager class provides methods to manage and retrieve AccountModel objects.
191
+ It inherits from MP_NodeManager for tree-like model implementation.
155
192
  """
156
193
 
157
194
  def get_queryset(self) -> AccountModelQuerySet:
@@ -407,49 +444,41 @@ def account_code_validator(value: str):
407
444
 
408
445
 
409
446
  class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
410
- """
411
- Django Ledger Base Account Model Abstract. This is the main abstract class which the Account Model database will
412
- inherit, and it contains the fields/columns/attributes which the said ledger table will have. In addition to the
413
- attributes mentioned below, it also has the fields/columns/attributes mentioned in the ParentChileMixin & the
414
- CreateUpdateMixIn. Read about these mixin here.
447
+ """ AccountModelAbstract
415
448
 
416
- Below are the fields specific to the accounts model.
449
+ Abstract class representing an Account Model.
417
450
 
418
451
  Attributes
419
452
  ----------
420
- uuid: UUID
421
- This is a unique primary key generated for the table. The default value of this field is uuid4().
422
-
423
- code: str
424
- Each account will have its own alphanumeric code.
425
- For example:
426
- * Cash Account -> Code 1010.
427
- * Inventory -> 1200.
428
- * Maximum Length allowed is 10.
429
-
430
- name: str
431
- This is the user defined name of the Account. the maximum length for Name of the ledger allowed is 100
432
-
433
- role: str
434
- Each Account needs to be assigned a certain Role. The exhaustive list of ROLES is defined in io.roles.
435
-
436
- balance_type: str
437
- Each account will have a default Account type i.e. Either Debit or Credit.
438
- For example:
439
- * Assets like Cash, Inventory, Accounts Receivable or Expenses like Rent, Salary will have balance_type=DEBIT.
440
- * Liabilities, Equities and Income like Payables, Loans, Income, Sales, Reserves will have balance_type=CREDIT.
441
-
442
- locked: bool
443
- This determines whether any transactions can be added in the account. Before making any update to the
444
- account, the account needs to be unlocked. Default value is set to False i.e. Unlocked.
445
-
446
- active: bool
447
- Determines whether the concerned account is active. Any Account can be used only when it is unlocked and
448
- Active. Default value is set to True.
449
-
450
- coa_model: ChartOfAccountsModel
451
- Each Accounts must be assigned a ChartOfAccountsModel. By default, one CoA will be created for each entity.
452
- However, the creating of a new AccountModel must have an explicit assignment of a ChartOfAccountModel.
453
+ BALANCE_TYPE : list
454
+ List of choices for the balance type of the account.
455
+
456
+ uuid : UUIDField
457
+ UUID field representing the primary key of the account.
458
+
459
+ code : CharField
460
+ CharField representing the account code.
461
+
462
+ name : CharField
463
+ CharField representing the account name.
464
+
465
+ role : CharField
466
+ CharField representing the account role.
467
+
468
+ role_default : BooleanField
469
+ BooleanField representing whether the account is a default account for the role.
470
+
471
+ balance_type : CharField
472
+ CharField representing the balance type of the account. Must be 'debit' or 'credit'.
473
+
474
+ locked : BooleanField
475
+ BooleanField representing whether the account is locked.
476
+
477
+ active : BooleanField
478
+ BooleanField representing whether the account is active.
479
+
480
+ coa_model : ForeignKey
481
+ ForeignKey representing the associated ChartOfAccountModel.
453
482
  """
454
483
  BALANCE_TYPE = [
455
484
  (CREDIT, _('Credit')),
@@ -485,7 +514,8 @@ class AccountModelAbstract(MP_Node, CreateUpdateMixIn):
485
514
  models.Index(fields=['balance_type']),
486
515
  models.Index(fields=['active']),
487
516
  models.Index(fields=['locked']),
488
- models.Index(fields=['coa_model'])
517
+ models.Index(fields=['coa_model', 'code']),
518
+ models.Index(fields=['code'])
489
519
  ]
490
520
 
491
521
  def __str__(self):
@@ -486,6 +486,8 @@ class BillModelAbstract(
486
486
 
487
487
  if self.can_generate_bill_number():
488
488
  self.generate_bill_number(commit=commit)
489
+ ledger_model.ledger_xid = f'bill-{self.bill_number.lower()}-{str(ledger_model.entity_id)[-5:]}'
490
+ ledger_model.save(update_fields=['ledger_xid'])
489
491
 
490
492
  self.clean()
491
493
 
@@ -460,6 +460,8 @@ class ChartOfAccountModelAbstract(SlugNameMixIn, CreateUpdateMixIn):
460
460
  raise ChartOfAccountsModelValidationError(
461
461
  message=f'Invalid Account Model {account_model} for CoA {self}'
462
462
  )
463
+ else:
464
+ account_model.coa_model = self
463
465
 
464
466
  if not root_account_qs:
465
467
  root_account_qs = self.get_coa_root_accounts_qs()
@@ -113,10 +113,10 @@ class EntityModelManager(MP_NodeManager):
113
113
 
114
114
  def get_queryset(self):
115
115
  """Sets the custom queryset as the default."""
116
- qs = EntityModelQuerySet(self.model).order_by('path')
116
+ qs = EntityModelQuerySet(self.model, using=self._db).order_by('path')
117
117
  return qs.order_by('path').select_related('admin', 'default_coa')
118
118
 
119
- def for_user(self, user_model):
119
+ def for_user(self, user_model, authorized_superuser: bool = False):
120
120
  """
121
121
  This QuerySet guarantees that Users do not access or operate on EntityModels that don't have access to.
122
122
  This is the recommended initial QuerySet.
@@ -125,6 +125,8 @@ class EntityModelManager(MP_NodeManager):
125
125
  ----------
126
126
  user_model
127
127
  The Django User Model making the request.
128
+ authorized_superuser
129
+ Allows any superuser to access the EntityModel. Default is False.
128
130
 
129
131
  Returns
130
132
  -------
@@ -134,7 +136,7 @@ class EntityModelManager(MP_NodeManager):
134
136
  2. Is a manager.
135
137
  """
136
138
  qs = self.get_queryset()
137
- if user_model.is_superuser:
139
+ if user_model.is_superuser and authorized_superuser:
138
140
  return qs
139
141
  return qs.filter(
140
142
  Q(admin=user_model) |
@@ -446,10 +446,11 @@ class InvoiceModelAbstract(
446
446
 
447
447
  if self.can_generate_invoice_number():
448
448
  self.generate_invoice_number(commit=commit)
449
+ ledger_model.ledger_xid=f'invoice-{self.invoice_number.lower()}-{str(ledger_model.entity_id)[-5:]}'
450
+ ledger_model.save(update_fields=['ledger_xid'])
449
451
 
450
452
  self.clean()
451
453
 
452
-
453
454
  if commit:
454
455
  self.save()
455
456
 
@@ -881,8 +881,6 @@ class ItemTransactionModelManager(models.Manager):
881
881
 
882
882
  def for_user(self, user_model):
883
883
  qs = self.get_queryset()
884
- if user_model.is_superuser:
885
- return qs
886
884
  return qs.filter(
887
885
  Q(item_model__entity__admin=user_model) |
888
886
  Q(item_model__entity__managers__in=[user_model])
@@ -890,6 +888,10 @@ class ItemTransactionModelManager(models.Manager):
890
888
 
891
889
  def for_entity(self, user_model, entity_slug):
892
890
  qs = self.for_user(user_model)
891
+ if isinstance(entity_slug, lazy_loader.get_entity_model()):
892
+ return qs.filter(
893
+ Q(item_model__entity=entity_slug)
894
+ )
893
895
  return qs.filter(
894
896
  Q(item_model__entity__slug__exact=entity_slug)
895
897
  )
@@ -184,13 +184,11 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
184
184
  _WRAPPED_MODEL_KEY = 'wrapped_model'
185
185
  uuid = models.UUIDField(default=uuid4, editable=False, primary_key=True)
186
186
  ledger_xid = models.SlugField(allow_unicode=True, max_length=150, null=True, blank=True,
187
- verbose_name=_('Ledger Slug'),
187
+ verbose_name=_('Ledger External ID'),
188
188
  help_text=_('User Defined Ledger ID'))
189
189
  name = models.CharField(max_length=150, null=True, blank=True, verbose_name=_('Ledger Name'))
190
190
 
191
- # todo: rename to entity_model...
192
191
  entity = models.ForeignKey('django_ledger.EntityModel',
193
- editable=False,
194
192
  on_delete=models.CASCADE,
195
193
  verbose_name=_('Ledger Entity'))
196
194
  posted = models.BooleanField(default=False, verbose_name=_('Posted Ledger'))
@@ -212,13 +210,21 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
212
210
  models.Index(fields=['entity']),
213
211
  models.Index(fields=['entity', 'posted']),
214
212
  models.Index(fields=['entity', 'locked']),
213
+ models.Index(fields=['entity', 'ledger_xid']),
214
+ models.Index(fields=['ledger_xid']),
215
215
  ]
216
216
  unique_together = [
217
217
  ('entity', 'ledger_xid')
218
218
  ]
219
219
 
220
220
  def __str__(self):
221
- return self.name
221
+ if self.name is not None:
222
+ ledger_str = f'LedgerModel: {self.name}'
223
+ elif self.ledger_xid is not None:
224
+ ledger_str = f'LedgerModel: {self.ledger_xid}'
225
+ else:
226
+ ledger_str = f'LedgerModel: {self.uuid}'
227
+ return f'{ledger_str} | Posted: {self.posted} | Locked: {self.locked}'
222
228
 
223
229
  def has_wrapped_model_info(self):
224
230
  if self.additional_info is not None:
@@ -516,7 +522,7 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
516
522
  je_model_qs.bulk_update(objs=je_model_qs, fields=['locked', 'updated'])
517
523
  return je_model_qs
518
524
 
519
- def unlock(self, commit: bool = False, **kwargs):
525
+ def unlock(self, commit: bool = False, raise_exception: bool = True, **kwargs):
520
526
  """
521
527
  Un-locks the LedgerModel.
522
528
 
@@ -525,13 +531,19 @@ class LedgerModelAbstract(CreateUpdateMixIn, IOMixIn):
525
531
  commit: bool
526
532
  If True, saves the LedgerModel instance instantly. Defaults to False.
527
533
  """
528
- if self.can_unlock():
529
- self.locked = False
530
- if commit:
531
- self.save(update_fields=[
532
- 'locked',
533
- 'updated'
534
- ])
534
+ if not self.can_unlock():
535
+ if raise_exception:
536
+ raise LedgerModelValidationError(
537
+ message=_(f'Ledger {self.name} cannot be un-locked. UUID: {self.uuid}')
538
+ )
539
+ return
540
+
541
+ self.locked = False
542
+ if commit:
543
+ self.save(update_fields=[
544
+ 'locked',
545
+ 'updated'
546
+ ])
535
547
 
536
548
  def hide(self, commit: bool = False, raise_exception: bool = True, **kwargs):
537
549
  if not self.can_hide():
@@ -487,7 +487,8 @@ class AccrualMixIn(models.Model):
487
487
  if ledger_model.locked:
488
488
  if raise_exception:
489
489
  raise ValidationError(f'Bill ledger {ledger_model.name} is already locked...')
490
- ledger_model.lock(commit)
490
+ return
491
+ ledger_model.lock(commit, raise_exception=raise_exception)
491
492
 
492
493
  def unlock_ledger(self, commit: bool = False, raise_exception: bool = True, **kwargs):
493
494
  """
@@ -501,10 +502,11 @@ class AccrualMixIn(models.Model):
501
502
  If True, raises ValidationError if LedgerModel already locked.
502
503
  """
503
504
  ledger_model = self.ledger
504
- if not ledger_model.locked:
505
+ if not ledger_model.is_locked():
505
506
  if raise_exception:
506
507
  raise ValidationError(f'Bill ledger {ledger_model.name} is already unlocked...')
507
- ledger_model.unlock(commit)
508
+ return
509
+ ledger_model.unlock(commit, raise_exception=raise_exception)
508
510
 
509
511
  # POST/UNPOST Ledger...
510
512
  def post_ledger(self, commit: bool = False, raise_exception: bool = True, **kwargs):
@@ -522,7 +524,8 @@ class AccrualMixIn(models.Model):
522
524
  if ledger_model.posted:
523
525
  if raise_exception:
524
526
  raise ValidationError(f'Bill ledger {ledger_model.name} is already posted...')
525
- ledger_model.post(commit)
527
+ return
528
+ ledger_model.post(commit, raise_exception=raise_exception)
526
529
 
527
530
  def unpost_ledger(self, commit: bool = False, raise_exception: bool = True, **kwargs):
528
531
  """
@@ -536,13 +539,14 @@ class AccrualMixIn(models.Model):
536
539
  If True, raises ValidationError if LedgerModel already locked.
537
540
  """
538
541
  ledger_model = self.ledger
539
- if not ledger_model.posted:
542
+ if not ledger_model.is_posted():
540
543
  if raise_exception:
541
544
  raise ValidationError(f'Bill ledger {ledger_model.name} is not posted...')
542
- ledger_model.post(commit)
545
+ return
546
+ ledger_model.post(commit, raise_exception=raise_exception)
543
547
 
544
548
  def migrate_state(self,
545
- # todo: remove usermodel param...
549
+ # todo: remove usermodel param...?
546
550
  user_model,
547
551
  entity_slug: str,
548
552
  itemtxs_qs: Optional[QuerySet] = None,
@@ -24,7 +24,7 @@ from django.contrib.auth import get_user_model
24
24
  from django.core.exceptions import ValidationError
25
25
  from django.core.validators import MinValueValidator
26
26
  from django.db import models
27
- from django.db.models import Q, QuerySet
27
+ from django.db.models import Q, QuerySet, F
28
28
  from django.db.models.signals import pre_save
29
29
  from django.utils.translation import gettext_lazy as _
30
30
 
@@ -49,35 +49,6 @@ class TransactionModelQuerySet(QuerySet):
49
49
  """
50
50
  A custom QuerySet class for TransactionModels implementing methods to effectively and safely read
51
51
  TransactionModels from the database.
52
-
53
- Methods
54
- -------
55
- posted() -> TransactionModelQuerySet:
56
- Fetches a QuerySet of posted transactions only.
57
-
58
- for_accounts(account_list: List[str or AccountModel]) -> TransactionModelQuerySet:
59
- Fetches a QuerySet of TransactionModels which AccountModel has a specific role.
60
-
61
- for_roles(role_list: Union[str, List[str]]) -> TransactionModelQuerySet:
62
- Fetches a QuerySet of TransactionModels which AccountModel has a specific role.
63
-
64
- for_unit(unit_slug: Union[str, EntityUnitModel]) -> TransactionModelQuerySet:
65
- Fetches a QuerySet of TransactionModels associated with a specific EntityUnitModel.
66
-
67
- for_activity(activity_list: Union[str, List[str]]) -> TransactionModelQuerySet:
68
- Fetches a QuerySet of TransactionModels associated with a specific activity or list of activities.
69
-
70
- to_date(to_date: Union[str, date, datetime]) -> TransactionModelQuerySet:
71
- Fetches a QuerySet of TransactionModels associated with a maximum date or timestamp filter.
72
-
73
- from_date(from_date: Union[str, date, datetime]) -> TransactionModelQuerySet:
74
- Fetches a QuerySet of TransactionModels associated with a minimum date or timestamp filter.
75
-
76
- not_closing_entry() -> TransactionModelQuerySet:
77
- Fetches a QuerySet of TransactionModels that are not part of a closing entry.
78
-
79
- is_closing_entry() -> TransactionModelQuerySet:
80
- Fetches a QuerySet of TransactionModels that are part of a closing entry.
81
52
  """
82
53
 
83
54
  def posted(self) -> QuerySet:
@@ -111,7 +82,7 @@ class TransactionModelQuerySet(QuerySet):
111
82
  TransactionModelQuerySet
112
83
  Returns a TransactionModelQuerySet with applied filters.
113
84
  """
114
- if len(account_list) > 0 and isinstance(account_list[0], str):
85
+ if isinstance(account_list, list) > 0 and isinstance(account_list[0], str):
115
86
  return self.filter(account__code__in=account_list)
116
87
  return self.filter(account__in=account_list)
117
88
 
@@ -238,7 +209,7 @@ class TransactionModelQuerySet(QuerySet):
238
209
  return self.filter(journal_entry__is_closing_entry=True)
239
210
 
240
211
 
241
- class TransactionModelAdmin(models.Manager):
212
+ class TransactionModelManager(models.Manager):
242
213
  """
243
214
  A manager class for the TransactionModel.
244
215
  """
@@ -276,8 +247,6 @@ class TransactionModelAdmin(models.Manager):
276
247
  ledger or the user is one of the managers of the entity associated with the transaction's ledger.
277
248
  """
278
249
  qs = self.get_queryset()
279
- if user_model.is_superuser:
280
- return qs
281
250
  return qs.filter(
282
251
  Q(journal_entry__ledger__entity__admin=user_model) |
283
252
  Q(journal_entry__ledger__entity__managers__in=[user_model])
@@ -531,7 +500,7 @@ class TransactionModelAbstract(CreateUpdateMixIn):
531
500
  verbose_name=_('Tx Description'),
532
501
  help_text=_('A description to be included with this individual transaction'))
533
502
 
534
- objects = TransactionModelAdmin()
503
+ objects = TransactionModelManager()
535
504
 
536
505
  class Meta:
537
506
  abstract = True
django_ledger/settings.py CHANGED
@@ -34,6 +34,7 @@ logger.info(f'Django Ledger GraphQL Enabled: {DJANGO_LEDGER_GRAPHQL_SUPPORT_ENAB
34
34
  DJANGO_LEDGER_USE_CLOSING_ENTRIES = getattr(settings, 'DJANGO_LEDGER_USE_CLOSING_ENTRIES', True)
35
35
  DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT = getattr(settings,
36
36
  'DJANGO_LEDGER_DEFAULT_CLOSING_ENTRY_CACHE_TIMEOUT', 3600)
37
+ DJANGO_LEDGER_AUTHORIZED_SUPERUSER = getattr(settings, 'DJANGO_LEDGER_AUTHORIZED_SUPERUSER', False)
37
38
  DJANGO_LEDGER_LOGIN_URL = getattr(settings, 'DJANGO_LEDGER_LOGIN_URL', settings.LOGIN_URL)
38
39
  DJANGO_LEDGER_BILL_NUMBER_LENGTH = getattr(settings, 'DJANGO_LEDGER_BILL_NUMBER_LENGTH', 10)
39
40
  DJANGO_LEDGER_INVOICE_NUMBER_LENGTH = getattr(settings, 'DJANGO_LEDGER_INVOICE_NUMBER_LENGTH', 10)
@@ -9,7 +9,7 @@ urlpatterns = [
9
9
 
10
10
  # DASHBOARD Views...
11
11
  path('<slug:entity_slug>/dashboard/',
12
- views.EntityModelDetailView.as_view(),
12
+ views.EntityModelDetailHandlerView.as_view(),
13
13
  name='entity-dashboard'),
14
14
  path('<slug:entity_slug>/dashboard/year/<int:year>/',
15
15
  views.FiscalYearEntityModelDashboardView.as_view(),
@@ -19,7 +19,7 @@ urlpatterns = [
19
19
 
20
20
  # DASHBOARD Views ...
21
21
  path('<slug:entity_slug>/dashboard/<slug:unit_slug>/',
22
- views.EntityModelDetailView.as_view(),
22
+ views.EntityModelDetailHandlerView.as_view(),
23
23
  name='unit-dashboard'),
24
24
  path('<slug:entity_slug>/dashboard/<slug:unit_slug>/year/<int:year>/',
25
25
  views.FiscalYearEntityModelDashboardView.as_view(),
@@ -159,9 +159,9 @@ class EntityDeleteView(DjangoLedgerSecurityMixIn, EntityModelModelViewQuerySetMi
159
159
 
160
160
 
161
161
  # DASHBOARD VIEWS START ----
162
- class EntityModelDetailView(DjangoLedgerSecurityMixIn,
163
- EntityUnitMixIn,
164
- RedirectView):
162
+ class EntityModelDetailHandlerView(DjangoLedgerSecurityMixIn,
163
+ EntityUnitMixIn,
164
+ RedirectView):
165
165
 
166
166
  def get_redirect_url(self, *args, **kwargs):
167
167
  loc_date = get_localdate()
@@ -182,14 +182,14 @@ class EntityModelDetailView(DjangoLedgerSecurityMixIn,
182
182
  })
183
183
 
184
184
 
185
- class FiscalYearEntityModelDashboardView(DjangoLedgerSecurityMixIn,
186
- EntityModelModelViewQuerySetMixIn,
187
- BaseDateNavigationUrlMixIn,
188
- UnpaidElementsMixIn,
189
- EntityUnitMixIn,
190
- DigestContextMixIn,
191
- YearlyReportMixIn,
192
- DetailView):
185
+ class EntityModelDetailBaseView(DjangoLedgerSecurityMixIn,
186
+ EntityModelModelViewQuerySetMixIn,
187
+ BaseDateNavigationUrlMixIn,
188
+ UnpaidElementsMixIn,
189
+ EntityUnitMixIn,
190
+ DigestContextMixIn,
191
+ YearlyReportMixIn,
192
+ DetailView):
193
193
  context_object_name = 'entity'
194
194
  slug_url_kwarg = 'entity_slug'
195
195
  template_name = 'django_ledger/entity/entity_dashboard.html'
@@ -203,7 +203,7 @@ class FiscalYearEntityModelDashboardView(DjangoLedgerSecurityMixIn,
203
203
  IO_DIGEST_EQUITY = True
204
204
 
205
205
  def get_context_data(self, **kwargs):
206
- context = super(FiscalYearEntityModelDashboardView, self).get_context_data(**kwargs)
206
+ context = super().get_context_data(**kwargs)
207
207
  entity_model: EntityModel = self.object
208
208
  context['page_title'] = entity_model.name
209
209
  context['header_title'] = entity_model.name
@@ -228,6 +228,12 @@ class FiscalYearEntityModelDashboardView(DjangoLedgerSecurityMixIn,
228
228
  return context
229
229
 
230
230
 
231
+ class FiscalYearEntityModelDashboardView(EntityModelDetailBaseView):
232
+ """
233
+ Entity Fiscal Year Dashboard View.
234
+ """
235
+
236
+
231
237
  class QuarterlyEntityDashboardView(FiscalYearEntityModelDashboardView, QuarterlyReportMixIn):
232
238
  """
233
239
  Entity Quarterly Dashboard View.
@@ -107,7 +107,6 @@ class LedgerModelCreateView(DjangoLedgerSecurityMixIn, LedgerModelModelViewQuery
107
107
  def form_valid(self, form):
108
108
  instance = form.save(commit=False)
109
109
  instance.entity = self.AUTHORIZED_ENTITY_MODEL
110
- self.object = form.save()
111
110
  return super().form_valid(form)
112
111
 
113
112
  def get_success_url(self):