diff --git a/api/factories.py b/api/factories.py index 1fa8504..8d8aa02 100644 --- a/api/factories.py +++ b/api/factories.py @@ -39,6 +39,8 @@ class TaxChargeFactory: @staticmethod def create_bulk_tax_charges(date): + tax_charges = TaxCharge.objects.filter(date=date) for value, _ in TaxCharge.Type.choices: - if not TaxCharge.objects.filter(date=date, type=value).exists(): + existing_tax_charge = [charge for charge in tax_charges if charge.type == value] + if len(existing_tax_charge) == 0: TaxCharge.objects.create(date=date, type=value, amount=0) diff --git a/api/forms.py b/api/forms.py index 7568d1e..997776f 100644 --- a/api/forms.py +++ b/api/forms.py @@ -271,6 +271,12 @@ def save(self): class BaseJournalEntryItemFormset(BaseModelFormSet): + def __init__(self, *args, **kwargs): + open_accounts = Account.objects.filter(is_closed=False) + open_accounts_choices = [(account.name, account.name) for account in open_accounts] + kwargs['form_kwargs'] = {'open_accounts_choices': open_accounts_choices} + super(BaseJournalEntryItemFormset, self).__init__(*args, **kwargs) + def get_entry_total(self): total = 0 for form in self.forms: @@ -322,12 +328,10 @@ class Meta: fields = ('account', 'amount') def __init__(self, *args, **kwargs): + open_accounts_choices = kwargs.pop('open_accounts_choices', []) super(JournalEntryItemForm, self).__init__(*args, **kwargs) self.fields['amount'].localize = True - closed_accounts = Account.objects.filter(is_closed=False) - self.fields['account'].choices = [ - (account.name, account.name) for account in closed_accounts - ] + self.fields['account'].choices = open_accounts_choices # Resolve the account name for the bound form if self.instance.pk and self.instance.account: @@ -408,7 +412,7 @@ def get_transactions(self): accounts=data['account'], date_from=data.get('date_from'), date_to=data.get('date_to') - ) + ).select_related('account') return queryset diff --git a/api/views/reconciliation_views.py b/api/views/reconciliation_views.py index 3193536..4b2199a 100644 --- a/api/views/reconciliation_views.py +++ b/api/views/reconciliation_views.py @@ -12,16 +12,16 @@ class ReconciliationTableMixin: - def _get_current_balance(self, reconciliation): - balance_sheet = BalanceSheet(reconciliation.date) - balance = balance_sheet.get_balance(reconciliation.account) + def _get_current_balance(self, balance_sheet, account): + balance = balance_sheet.get_balance(account) return balance - def get_reconciliation_html(self, reconciliations): - reconciliations = reconciliations.order_by('account') + def get_reconciliation_html(self, reconciliations, date): + reconciliations = reconciliations.select_related('account').order_by('account') + balance_sheet = BalanceSheet(date) for reconciliation in reconciliations: - reconciliation.current_balance = self._get_current_balance(reconciliation) + reconciliation.current_balance = self._get_current_balance(balance_sheet, reconciliation.account) ReconciliationFormset = modelformset_factory(Reconciliation, ReconciliationForm, extra=0) @@ -59,7 +59,7 @@ def get(self, request, *args, **kwargs): ReconciliationFactory.create_bulk_reconciliations(date=form.cleaned_data['date']) reconciliations = form.get_reconciliations() - reconciliations_table = self.get_reconciliation_html(reconciliations) + reconciliations_table = self.get_reconciliation_html(reconciliations, date=form.cleaned_data['date']) return HttpResponse(reconciliations_table) # Loads full page @@ -74,7 +74,7 @@ def get(self, request, *args, **kwargs): ReconciliationFactory.create_bulk_reconciliations(date=initial_date) reconciliations = Reconciliation.objects.filter(date=initial_date) - reconciliation_table = self.get_reconciliation_html(reconciliations) + reconciliation_table = self.get_reconciliation_html(reconciliations, date=initial_date) context = { 'reconciliation_table': reconciliation_table, 'filter_form': render_to_string( @@ -101,6 +101,6 @@ def post(self, request): if filter_form.is_valid(): reconciliations = filter_form.get_reconciliations() - reconciliation_table = self.get_reconciliation_html(reconciliations) + reconciliation_table = self.get_reconciliation_html(reconciliations, date=filter_form.cleaned_data['date']) return HttpResponse(reconciliation_table) \ No newline at end of file diff --git a/api/views/tax_views.py b/api/views/tax_views.py index d04ffe6..535f039 100644 --- a/api/views/tax_views.py +++ b/api/views/tax_views.py @@ -4,18 +4,21 @@ from django.views import View from django.shortcuts import get_object_or_404 from django.contrib.auth.mixins import LoginRequiredMixin -from api.models import TaxCharge +from api.models import TaxCharge from api.forms import TaxChargeFilterForm, TaxChargeForm from api.statement import IncomeStatement from api.factories import TaxChargeFactory from api import utils + class TaxChargeMixIn: - def _add_tax_rate_and_charge(self, tax_charge, current_taxable_income=None): - last_day_of_month = tax_charge.date - first_day_of_month = date(last_day_of_month.year, last_day_of_month.month, 1) - taxable_income = IncomeStatement(tax_charge.date, first_day_of_month).get_taxable_income() + def _get_taxable_income(self, end_date): + first_day_of_month = date(end_date.year, end_date.month, 1) + taxable_income = IncomeStatement(end_date, first_day_of_month).get_taxable_income() + return taxable_income + + def _add_tax_rate_and_charge(self, tax_charge, taxable_income, current_taxable_income=None): tax_charge.taxable_income = taxable_income tax_charge.tax_rate = None if taxable_income == 0 else tax_charge.amount / taxable_income if current_taxable_income and tax_charge.tax_rate: @@ -44,7 +47,7 @@ def get_tax_form_html(self, tax_charge=None, last_day_of_month=None): current_taxable_income = income_statement.get_taxable_income() - for latest_tax_charge in [latest_federal_tax_charge,latest_state_tax_charge]: + for latest_tax_charge in [latest_federal_tax_charge, latest_state_tax_charge]: if latest_tax_charge: self._add_tax_rate_and_charge(latest_tax_charge, current_taxable_income) @@ -60,11 +63,17 @@ def get_tax_form_html(self, tax_charge=None, last_day_of_month=None): form_html = render_to_string(form_template, context) return form_html - def get_tax_table_html(self, tax_charges): + def get_tax_table_html(self, tax_charges, end_date): - tax_charges = tax_charges.order_by('date','type') + tax_charges = tax_charges.select_related('transaction', 'transaction__account').order_by('date', 'type') + tax_dates = [] + taxable_income = None for tax_charge in tax_charges: - self._add_tax_rate_and_charge(tax_charge) + # Limit the number of IncomeStatement objects we need to make + if tax_charge.date not in tax_dates: + taxable_income = self._get_taxable_income(tax_charge.date) + self._add_tax_rate_and_charge(tax_charge=tax_charge, taxable_income=taxable_income) + tax_charge.transaction_string = str(tax_charge.transaction) tax_charge_table_html = render_to_string( 'api/tables/tax-table.html', @@ -73,6 +82,7 @@ def get_tax_table_html(self, tax_charges): return tax_charge_table_html + class TaxChargeTableView(TaxChargeMixIn, LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'next' @@ -92,6 +102,7 @@ def get(self, request, *args, **kwargs): return HttpResponse(html) + class TaxChargeFormView(TaxChargeMixIn, LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'next' @@ -106,6 +117,7 @@ def get(self, request, pk=None, *args, **kwargs): return HttpResponse(form_html) + # Loads full page class TaxesView(TaxChargeMixIn, LoginRequiredMixin, View): login_url = '/login/' @@ -117,10 +129,10 @@ def get(self, request, *args, **kwargs): TaxChargeFactory.create_bulk_tax_charges(date=initial_end_date) six_months_ago = utils.get_last_days_of_month_tuples()[5][0] - tax_charges = TaxCharge.objects.filter(date__gte=six_months_ago,date__lte=initial_end_date) + tax_charges = TaxCharge.objects.filter(date__gte=six_months_ago, date__lte=initial_end_date) context = { - 'tax_charge_table': self.get_tax_table_html(tax_charges), + 'tax_charge_table': self.get_tax_table_html(tax_charges=tax_charges, end_date=initial_end_date), 'form': self.get_tax_form_html(last_day_of_month=utils.get_last_day_of_last_month()), 'filter_form': self.get_tax_filter_form_html() } diff --git a/api/views/transaction_views.py b/api/views/transaction_views.py index c6299bf..61872b6 100644 --- a/api/views/transaction_views.py +++ b/api/views/transaction_views.py @@ -40,7 +40,7 @@ def get_filter_form_html_and_objects( transaction_types=transaction_type, date_from=date_from, date_to=date_to - ) + ).select_related('account') context = { 'filter_form': form, diff --git a/ledger/settings.py b/ledger/settings.py index 98d0b61..99b47cf 100644 --- a/ledger/settings.py +++ b/ledger/settings.py @@ -49,12 +49,14 @@ 'django.contrib.humanize', 'api', 'import_export', + 'debug_toolbar' ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', + 'debug_toolbar.middleware.DebugToolbarMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', @@ -62,6 +64,10 @@ 'whitenoise.middleware.WhiteNoiseMiddleware', ] +INTERNAL_IPS = [ + '127.0.0.1', +] + ROOT_URLCONF = 'ledger.urls' TEMPLATES = [ diff --git a/ledger/templates/api/tables/tax-table.html b/ledger/templates/api/tables/tax-table.html index cc6bf45..b8d53c0 100644 --- a/ledger/templates/api/tables/tax-table.html +++ b/ledger/templates/api/tables/tax-table.html @@ -18,7 +18,7 @@ {% for tax_charge in tax_charges %} {{ tax_charge.type }} - {{ tax_charge.transaction }} + {{ tax_charge.transaction_string }} {{ tax_charge.date }} ${{ tax_charge.amount|floatformat:2|intcomma }} ${{ tax_charge.taxable_income|floatformat:2|intcomma }} diff --git a/ledger/urls.py b/ledger/urls.py index 9cac0e3..6980285 100644 --- a/ledger/urls.py +++ b/ledger/urls.py @@ -1,6 +1,8 @@ +import debug_toolbar from django.contrib import admin -from django.urls import path +from django.urls import path, include from django.contrib.auth.views import LoginView +from django.conf import settings from api.views.frontend_views import TrendView, UploadTransactionsView, IndexView from api.views.transaction_views import LinkTransactionsContentView, TransactionContentView, TransactionFormView, JournalEntryView, LinkTransactionsView, JournalEntryFormView, JournalEntryTableView, TransactionsView from api.views.tax_views import TaxChargeTableView,TaxChargeFormView, TaxesView @@ -50,3 +52,9 @@ path('amortization/amortization-form//', AmortizationFormView.as_view(), name='amortization-form'), path('amortization/amortize-form//', AmortizeFormView.as_view(), name='amortize-form'), ] + +if settings.DEBUG: + + urlpatterns = [ + path('__debug__/', include(debug_toolbar.urls)), + ] + urlpatterns diff --git a/requirements.txt b/requirements.txt index e02da0d..56abcdb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,12 +8,14 @@ dill==0.3.6 dj-database-url==1.2.0 Django==4.1.6 django-crispy-forms==2.0 +django-debug-toolbar==4.2.0 django-import-export==3.3.5 djangorestframework==3.14.0 drf-extra-fields==3.4.1 et-xmlfile==1.1.0 factory-boy==3.3.0 Faker==22.2.0 +flake8==7.0.0 gunicorn==20.1.0 isort==5.12.0 lazy-object-proxy==1.9.0 @@ -24,6 +26,8 @@ openpyxl==3.1.2 Pillow==9.4.0 platformdirs==3.0.0 psycopg2-binary==2.9.5 +pycodestyle==2.11.1 +pyflakes==3.2.0 pylint==2.16.1 pylint-django==2.5.3 pylint-plugin-utils==0.7