diff --git a/.gitignore b/.gitignore
index 1857c4f..325d73c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,10 @@
venv/
online/
.idea/
-.DS_Store
\ No newline at end of file
+.DS_Store
+
+# local files
+server/
+cashonly/web/static/bootstrap/**/*
+db.sqlite3
+manage.py
\ No newline at end of file
diff --git a/cashonly/core/__init__.py b/cashonly/core/__init__.py
index eb23024..51723d7 100644
--- a/cashonly/core/__init__.py
+++ b/cashonly/core/__init__.py
@@ -2,11 +2,11 @@ from django.apps import AppConfig
class CashonlyCoreAppConfig(AppConfig):
- name = 'cashonly.core'
- label = 'cashonly_core'
+ name = "cashonly.core"
+ label = "cashonly_core"
def ready(self):
from . import services
-default_app_config = 'cashonly.core.CashonlyCoreAppConfig'
+default_app_config = "cashonly.core.CashonlyCoreAppConfig"
diff --git a/cashonly/core/admin.py b/cashonly/core/admin.py
index 63719bd..c768d98 100644
--- a/cashonly/core/admin.py
+++ b/cashonly/core/admin.py
@@ -3,26 +3,23 @@ from cashonly.core.models import *
from cashonly.core.formfields import DigitField
from cashonly.core.services import AccountManager
from django import forms
-from django.template.defaultfilters import escape
-from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext as _
-from django.utils.translation import ugettext_lazy
-from django.utils.translation import ugettext_noop
+from django.urls import reverse
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
class AccountForm(forms.ModelForm):
- credit_change = forms.DecimalField(max_digits=5, decimal_places=2,
- required=False,
- label=ugettext_lazy('amount'))
+ credit_change = forms.DecimalField(
+ max_digits=5, decimal_places=2, required=False, label=gettext_lazy("amount")
+ )
- credit_change_comment = forms.CharField(max_length=64, required=False,
- label=ugettext_lazy('comment'))
+ credit_change_comment = forms.CharField(
+ max_length=64, required=False, label=gettext_lazy("comment")
+ )
- pin_change = DigitField(min_length=4, required=False,
- label=ugettext_lazy('PIN'))
+ pin_change = DigitField(min_length=4, required=False, label=gettext_lazy("PIN"))
- pin_empty = forms.BooleanField(required=False,
- label=ugettext_lazy('clear PIN'))
+ pin_empty = forms.BooleanField(required=False, label=gettext_lazy("clear PIN"))
class Meta:
model = Account
@@ -32,21 +29,31 @@ class AccountForm(forms.ModelForm):
class AccountAdmin(admin.ModelAdmin):
- list_display = ('user', 'card_number', 'credit', 'debit_limit',
- 'transaction_link')
+ list_display = ("user", "card_number", "credit", "debit_limit", "transaction_link")
form = AccountForm
- readonly_fields = ('user', 'credit',)
+ readonly_fields = (
+ "user",
+ "credit",
+ )
fieldsets = (
- (None, {
- 'fields': ('user', 'card_number', 'credit', 'debit_limit',
- 'avatar'),
- }),
- (ugettext_lazy('credit change'), {
- 'fields': ('credit_change', 'credit_change_comment'),
- }),
- (ugettext_lazy('change PIN'), {
- 'fields': ('pin_change', 'pin_empty'),
- }),
+ (
+ None,
+ {
+ "fields": ("user", "card_number", "credit", "debit_limit", "avatar"),
+ },
+ ),
+ (
+ gettext_lazy("credit change"),
+ {
+ "fields": ("credit_change", "credit_change_comment"),
+ },
+ ),
+ (
+ gettext_lazy("change PIN"),
+ {
+ "fields": ("pin_change", "pin_empty"),
+ },
+ ),
)
# Disable manual creation of accounts.
@@ -55,26 +62,28 @@ class AccountAdmin(admin.ModelAdmin):
return False
def transaction_link(self, account):
- return '%s' % \
- (reverse("admin:cashonly_core_transaction_changelist"), account.id,
- _('Transactions'))
+ return '%s' % (
+ reverse("admin:cashonly_core_transaction_changelist"),
+ account.id,
+ _("Transactions"),
+ )
- transaction_link.short_description = ugettext_lazy('Transaction link')
+ transaction_link.short_description = gettext_lazy("Transaction link")
transaction_link.allow_tags = True
def save_model(self, request, obj, form, change):
accmgr = AccountManager(obj)
- pin = form.cleaned_data['pin_change']
- pin_empty = form.cleaned_data['pin_empty']
+ pin = form.cleaned_data["pin_change"]
+ pin_empty = form.cleaned_data["pin_empty"]
if pin_empty:
accmgr.clear_pin()
elif pin is not None and len(pin) != 0:
accmgr.set_pin(pin)
- amount = form.cleaned_data['credit_change']
- comment = form.cleaned_data['credit_change_comment']
+ amount = form.cleaned_data["credit_change"]
+ comment = form.cleaned_data["credit_change_comment"]
if amount is not None and amount != 0:
accmgr.change_credit(amount, request.user, comment)
@@ -89,32 +98,32 @@ class ProductBarcodeInline(admin.TabularInline):
class ProductAdmin(admin.ModelAdmin):
- list_display = ('name', 'category', 'price', 'active')
- list_filter = ['category', 'active']
- ordering = ('-active', 'name')
+ list_display = ("name", "category", "price", "active")
+ list_filter = ["category", "active"]
+ ordering = ("-active", "name")
inlines = [ProductBarcodeInline]
- fields = ('name', 'price', 'active', 'category', 'image')
+ fields = ("name", "price", "active", "category", "image")
class ProductCategoryAdmin(admin.ModelAdmin):
- list_display = ('name', 'comment')
+ list_display = ("name", "comment")
class SalesLogEntryAdmin(admin.ModelAdmin):
- list_display = ('account', 'timestamp', 'product', 'count', 'unit_price')
- list_filter = ['account', 'timestamp', 'product']
+ list_display = ("account", "timestamp", "product", "count", "unit_price")
+ list_filter = ["account", "timestamp", "product"]
# Make sales log entries completely read-only
readonly_fields = list(map(lambda f: f.name, SalesLogEntry._meta.fields))
class TransactionAdmin(admin.ModelAdmin):
- list_display = ('account', 'timestamp', 'subject', 'description', 'amount')
- list_filter = ['account', 'timestamp', 'subject']
+ list_display = ("account", "timestamp", "subject", "description", "amount")
+ list_filter = ["account", "timestamp", "subject"]
# Disable mass deletion in the overview page
actions = None
- date_hierarchy = 'timestamp'
+ date_hierarchy = "timestamp"
# Disable tampering with the transactions completely.
def has_add_permission(self, request):
@@ -131,11 +140,12 @@ class TransactionAdmin(admin.ModelAdmin):
# Needed to not trigger an ImproperlyConfigured exception.
# FIXME: a bit too hacky
def changelist_view(self, request, extra_context=None):
- self.list_display_links = (None, )
+ self.list_display_links = (None,)
return super(TransactionAdmin, self).changelist_view(
- request,
- extra_context=None,
- )
+ request,
+ extra_context=None,
+ )
+
admin.site.register(Account, AccountAdmin)
admin.site.register(Product, ProductAdmin)
diff --git a/cashonly/core/apps.py b/cashonly/core/apps.py
index 1224743..0006ff0 100644
--- a/cashonly/core/apps.py
+++ b/cashonly/core/apps.py
@@ -2,5 +2,5 @@ from django.apps import AppConfig
class CashonlyCoreConfig(AppConfig):
- name = 'cashonly.core'
- label = 'cashonly_core'
+ name = "cashonly.core"
+ label = "cashonly_core"
diff --git a/cashonly/core/auth.py b/cashonly/core/auth.py
index f12f3de..61762b2 100644
--- a/cashonly/core/auth.py
+++ b/cashonly/core/auth.py
@@ -6,9 +6,9 @@ from cashonly.core.services import AccountManager
class UsernameCardnumberPinBackend(object):
def authenticate(self, username=None, card_number=None, pin=None):
if username is not None and card_number is not None:
- raise ValueError('username and card_number are mutually exclusive')
+ raise ValueError("username and card_number are mutually exclusive")
if username is None and card_number is None:
- raise ValueError('either username and card_number is required')
+ raise ValueError("either username and card_number is required")
try:
if username is not None:
diff --git a/cashonly/core/formfields.py b/cashonly/core/formfields.py
index c9049a5..7b784f7 100644
--- a/cashonly/core/formfields.py
+++ b/cashonly/core/formfields.py
@@ -1,6 +1,6 @@
from django.forms import CharField
from django.core.exceptions import ValidationError
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from django.core.validators import EMPTY_VALUES
@@ -9,6 +9,6 @@ class DigitField(CharField):
super(DigitField, self).clean(value)
if value not in EMPTY_VALUES and not value.isdigit():
- raise ValidationError(_('Please enter only digits.'))
+ raise ValidationError(_("Please enter only digits."))
return value
diff --git a/cashonly/core/management/commands/dailydigest.py b/cashonly/core/management/commands/dailydigest.py
index ddbfd97..0f84a3b 100644
--- a/cashonly/core/management/commands/dailydigest.py
+++ b/cashonly/core/management/commands/dailydigest.py
@@ -1,75 +1,90 @@
from cashonly.core.models import *
from django.conf import settings
from django.core.mail import send_mass_mail
-from django.core.management.base import BaseCommand, CommandError
-from django.template import Context
+from django.core.management.base import BaseCommand
from django.template.loader import get_template
from django.utils import translation
from django.utils.dateformat import DateFormat
from django.utils.formats import get_format
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
import datetime
class Command(BaseCommand):
- help = 'Sends out a digest of transactions to recently active users'
+ help = "Sends out a digest of transactions to recently active users"
def handle(self, **options):
- translation.activate('de')
+ translation.activate("de")
- tpl = get_template('cashonly/core/daily_digest.txt')
+ tpl = get_template("cashonly/core/daily_digest.txt")
messages = []
for a in Account.objects.filter(daily_digest=True):
- name = '%s %s' % (a.user.first_name, a.user.last_name)
+ name = "%s %s" % (a.user.first_name, a.user.last_name)
context = {
- 'name': name,
- 'credit': a.credit,
- 'range': settings.CASHONLY_DAILY_DIGEST_RANGE_HOURS,
- 'url': settings.CASHONLY_USERSETTINGS_URL,
+ "name": name,
+ "credit": a.credit,
+ "range": settings.CASHONLY_DAILY_DIGEST_RANGE_HOURS,
+ "url": settings.CASHONLY_USERSETTINGS_URL,
}
min_ts = datetime.datetime.now() - datetime.timedelta(
hours=settings.CASHONLY_DAILY_DIGEST_RANGE_HOURS
)
- transactions = Transaction.objects.filter(account=a) \
- .filter(timestamp__gte=min_ts)
+ transactions = Transaction.objects.filter(account=a).filter(
+ timestamp__gte=min_ts
+ )
if transactions.count() > 0:
- lengths = {'timestamp': len(_('date')),
- 'description': len(_('subject')),
- 'amount': len(_('amount'))}
+ lengths = {
+ "timestamp": len(_("date")),
+ "description": len(_("subject")),
+ "amount": len(_("amount")),
+ }
sum = 0
for t in transactions:
- lengths['timestamp'] = \
- max(lengths['timestamp'], len(DateFormat(t.timestamp)
- .format(get_format('SHORT_DATETIME_FORMAT'))))
- lengths['description'] = \
- max(lengths['description'], len(t.description))
- lengths['amount'] = \
- max(lengths['amount'], len(str(t.amount)))
- t.description = t.description.split('\n')
+ lengths["timestamp"] = max(
+ lengths["timestamp"],
+ len(
+ DateFormat(t.timestamp).format(
+ get_format("SHORT_DATETIME_FORMAT")
+ )
+ ),
+ )
+ lengths["description"] = max(
+ lengths["description"], len(t.description)
+ )
+ lengths["amount"] = max(lengths["amount"], len(str(t.amount)))
+ t.description = t.description.split("\n")
sum += t.amount
- lengths['sum'] = lengths['timestamp'] + \
- lengths['description'] + lengths['amount']
- context['lengths'] = lengths
- context['tl'] = transactions
- context['sum'] = sum
+ lengths["sum"] = (
+ lengths["timestamp"] + lengths["description"] + lengths["amount"]
+ )
+ context["lengths"] = lengths
+ context["tl"] = transactions
+ context["sum"] = sum
if a.user.email is not None and len(a.user.email) > 0:
- rcpts = ['%s <%s>' % (name, a.user.email)]
+ rcpts = ["%s <%s>" % (name, a.user.email)]
else:
- self.stdout.write(self.style.WARNING(
- 'User "%s" has no Email address.' % (a.user.username)
- ))
+ self.stdout.write(
+ self.style.WARNING(
+ 'User "%s" has no Email address.' % (a.user.username)
+ )
+ )
continue
- messages.append(('%s%s' % (settings.EMAIL_SUBJECT_PREFIX,
- _('Account Statement')),
- tpl.render(context),
- settings.DEFAULT_FROM_EMAIL, rcpts))
+ messages.append(
+ (
+ "%s%s"
+ % (settings.EMAIL_SUBJECT_PREFIX, _("Account Statement")),
+ tpl.render(context),
+ settings.DEFAULT_FROM_EMAIL,
+ rcpts,
+ )
+ )
send_mass_mail(tuple(messages))
diff --git a/cashonly/core/management/commands/debtreminder.py b/cashonly/core/management/commands/debtreminder.py
index 950a25b..f3ad25b 100644
--- a/cashonly/core/management/commands/debtreminder.py
+++ b/cashonly/core/management/commands/debtreminder.py
@@ -1,4 +1,3 @@
-
from cashonly.models import *
from django.conf import settings
from django.core.mail import send_mass_mail
@@ -6,28 +5,32 @@ from django.core.management.base import NoArgsCommand
from django.template import Context
from django.template.loader import get_template
from django.utils import translation
-from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
class Command(NoArgsCommand):
- help = 'Sends a reminder mail to every with a negative credit'
+ help = "Sends a reminder mail to every with a negative credit"
def handle_noargs(self, **options):
- translation.activate('de')
+ translation.activate("de")
- tpl = get_template('cashonly/debt_reminder.txt')
+ tpl = get_template("cashonly/debt_reminder.txt")
messages = []
for a in Account.objects.all():
if a.credit < 0:
- name = '%s %s' % (a.user.first_name, a.user.last_name)
- context = {'name': name, 'credit': a.credit}
-
- rcpts = ['%s <%s>' % (name, a.user.email)]
-
- messages.append(('%s%s' % (settings.EMAIL_SUBJECT_PREFIX,
- _('Debt Reminder')),
- tpl.render(Context(context)),
- settings.DEFAULT_FROM_EMAIL, rcpts))
+ name = "%s %s" % (a.user.first_name, a.user.last_name)
+ context = {"name": name, "credit": a.credit}
+
+ rcpts = ["%s <%s>" % (name, a.user.email)]
+
+ messages.append(
+ (
+ "%s%s" % (settings.EMAIL_SUBJECT_PREFIX, _("Debt Reminder")),
+ tpl.render(Context(context)),
+ settings.DEFAULT_FROM_EMAIL,
+ rcpts,
+ )
+ )
send_mass_mail(tuple(messages))
diff --git a/cashonly/core/migrations/0001_initial.py b/cashonly/core/migrations/0001_initial.py
index c6d19d2..374bb4c 100644
--- a/cashonly/core/migrations/0001_initial.py
+++ b/cashonly/core/migrations/0001_initial.py
@@ -17,93 +17,259 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
- name='Account',
+ name="Account",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('card_number', models.CharField(blank=True, max_length=32, null=True, unique=True, verbose_name='card number')),
- ('pin', models.CharField(blank=True, max_length=32, verbose_name='PIN')),
- ('daily_digest', models.BooleanField(default=True, verbose_name='daily digest')),
- ('credit', models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='credit')),
- ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "card_number",
+ models.CharField(
+ blank=True,
+ max_length=32,
+ null=True,
+ unique=True,
+ verbose_name="card number",
+ ),
+ ),
+ (
+ "pin",
+ models.CharField(blank=True, max_length=32, verbose_name="PIN"),
+ ),
+ (
+ "daily_digest",
+ models.BooleanField(default=True, verbose_name="daily digest"),
+ ),
+ (
+ "credit",
+ models.DecimalField(
+ decimal_places=2, default=0, max_digits=5, verbose_name="credit"
+ ),
+ ),
+ (
+ "user",
+ models.OneToOneField(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
],
options={
- 'verbose_name': 'account',
- 'verbose_name_plural': 'accounts',
+ "verbose_name": "account",
+ "verbose_name_plural": "accounts",
},
),
migrations.CreateModel(
- name='Product',
+ name="Product",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=32, unique=True, verbose_name='name')),
- ('price', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='price')),
- ('active', models.BooleanField(default=True, verbose_name='active')),
- ('image', models.ImageField(blank=True, null=True, upload_to='products', verbose_name='image')),
- ('image_thumbnail', models.ImageField(blank=True, null=True, upload_to='products_thumb', verbose_name='image')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "name",
+ models.CharField(max_length=32, unique=True, verbose_name="name"),
+ ),
+ (
+ "price",
+ models.DecimalField(
+ decimal_places=2, max_digits=5, verbose_name="price"
+ ),
+ ),
+ ("active", models.BooleanField(default=True, verbose_name="active")),
+ (
+ "image",
+ models.ImageField(
+ blank=True,
+ null=True,
+ upload_to="products",
+ verbose_name="image",
+ ),
+ ),
+ (
+ "image_thumbnail",
+ models.ImageField(
+ blank=True,
+ null=True,
+ upload_to="products_thumb",
+ verbose_name="image",
+ ),
+ ),
],
options={
- 'verbose_name': 'product',
- 'verbose_name_plural': 'products',
+ "verbose_name": "product",
+ "verbose_name_plural": "products",
},
),
migrations.CreateModel(
- name='ProductBarcode',
+ name="ProductBarcode",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('barcode', models.CharField(max_length=32, unique=True, verbose_name='barcode')),
- ('comment', models.CharField(blank=True, max_length=128, verbose_name='comment')),
- ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cashonly_core.Product', verbose_name='product')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "barcode",
+ models.CharField(
+ max_length=32, unique=True, verbose_name="barcode"
+ ),
+ ),
+ (
+ "comment",
+ models.CharField(
+ blank=True, max_length=128, verbose_name="comment"
+ ),
+ ),
+ (
+ "product",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="cashonly_core.Product",
+ verbose_name="product",
+ ),
+ ),
],
options={
- 'verbose_name': 'barcode',
- 'verbose_name_plural': 'barcodes',
+ "verbose_name": "barcode",
+ "verbose_name_plural": "barcodes",
},
),
migrations.CreateModel(
- name='ProductCategory',
+ name="ProductCategory",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=32, unique=True, verbose_name='name')),
- ('comment', models.CharField(blank=True, max_length=128, verbose_name='comment')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "name",
+ models.CharField(max_length=32, unique=True, verbose_name="name"),
+ ),
+ (
+ "comment",
+ models.CharField(
+ blank=True, max_length=128, verbose_name="comment"
+ ),
+ ),
],
options={
- 'verbose_name': 'product category',
- 'verbose_name_plural': 'product categories',
+ "verbose_name": "product category",
+ "verbose_name_plural": "product categories",
},
),
migrations.CreateModel(
- name='SalesLogEntry',
+ name="SalesLogEntry",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('count', models.IntegerField(verbose_name='count')),
- ('unit_price', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='unit price')),
- ('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='timestamp')),
- ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cashonly_core.Account', verbose_name='account')),
- ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cashonly_core.Product', verbose_name='product')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("count", models.IntegerField(verbose_name="count")),
+ (
+ "unit_price",
+ models.DecimalField(
+ decimal_places=2, max_digits=5, verbose_name="unit price"
+ ),
+ ),
+ (
+ "timestamp",
+ models.DateTimeField(auto_now_add=True, verbose_name="timestamp"),
+ ),
+ (
+ "account",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="cashonly_core.Account",
+ verbose_name="account",
+ ),
+ ),
+ (
+ "product",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="cashonly_core.Product",
+ verbose_name="product",
+ ),
+ ),
],
options={
- 'verbose_name': 'sales log entry',
- 'verbose_name_plural': 'sales log entries',
+ "verbose_name": "sales log entry",
+ "verbose_name_plural": "sales log entries",
},
),
migrations.CreateModel(
- name='Transaction',
+ name="Transaction",
fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='timestamp')),
- ('subject', models.CharField(max_length=32, verbose_name='subject')),
- ('description', models.TextField(verbose_name='description')),
- ('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='amount')),
- ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cashonly_core.Account', verbose_name='account')),
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "timestamp",
+ models.DateTimeField(auto_now_add=True, verbose_name="timestamp"),
+ ),
+ ("subject", models.CharField(max_length=32, verbose_name="subject")),
+ ("description", models.TextField(verbose_name="description")),
+ (
+ "amount",
+ models.DecimalField(
+ decimal_places=2, max_digits=5, verbose_name="amount"
+ ),
+ ),
+ (
+ "account",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to="cashonly_core.Account",
+ verbose_name="account",
+ ),
+ ),
],
options={
- 'verbose_name': 'transaction',
- 'verbose_name_plural': 'transactions',
+ "verbose_name": "transaction",
+ "verbose_name_plural": "transactions",
},
),
migrations.AddField(
- model_name='product',
- name='category',
- field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cashonly_core.ProductCategory', verbose_name='category'),
+ model_name="product",
+ name="category",
+ field=models.ForeignKey(
+ blank=True,
+ null=True,
+ on_delete=django.db.models.deletion.CASCADE,
+ to="cashonly_core.ProductCategory",
+ verbose_name="category",
+ ),
),
]
diff --git a/cashonly/core/migrations/0002_account_debit_limit.py b/cashonly/core/migrations/0002_account_debit_limit.py
index 7ddd9e6..eba59a9 100644
--- a/cashonly/core/migrations/0002_account_debit_limit.py
+++ b/cashonly/core/migrations/0002_account_debit_limit.py
@@ -8,13 +8,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('cashonly_core', '0001_initial'),
+ ("cashonly_core", "0001_initial"),
]
operations = [
migrations.AddField(
- model_name='account',
- name='debit_limit',
- field=models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='Debit Limit'),
+ model_name="account",
+ name="debit_limit",
+ field=models.DecimalField(
+ decimal_places=2, default=0, max_digits=5, verbose_name="Debit Limit"
+ ),
),
]
diff --git a/cashonly/core/migrations/0003_debit_limit_null_default.py b/cashonly/core/migrations/0003_debit_limit_null_default.py
index 57e839f..b43b1ed 100644
--- a/cashonly/core/migrations/0003_debit_limit_null_default.py
+++ b/cashonly/core/migrations/0003_debit_limit_null_default.py
@@ -8,13 +8,20 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('cashonly_core', '0002_account_debit_limit'),
+ ("cashonly_core", "0002_account_debit_limit"),
]
operations = [
migrations.AlterField(
- model_name='account',
- name='debit_limit',
- field=models.DecimalField(blank=True, decimal_places=2, default=None, max_digits=5, null=True, verbose_name='debit limit'),
+ model_name="account",
+ name="debit_limit",
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=2,
+ default=None,
+ max_digits=5,
+ null=True,
+ verbose_name="debit limit",
+ ),
),
]
diff --git a/cashonly/core/migrations/0004_account_avatar.py b/cashonly/core/migrations/0004_account_avatar.py
index ec1aa9e..0643c02 100644
--- a/cashonly/core/migrations/0004_account_avatar.py
+++ b/cashonly/core/migrations/0004_account_avatar.py
@@ -8,13 +8,15 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
- ('cashonly_core', '0003_debit_limit_null_default'),
+ ("cashonly_core", "0003_debit_limit_null_default"),
]
operations = [
migrations.AddField(
- model_name='account',
- name='avatar',
- field=models.ImageField(blank=True, null=True, upload_to='avatars', verbose_name='avatar'),
+ model_name="account",
+ name="avatar",
+ field=models.ImageField(
+ blank=True, null=True, upload_to="avatars", verbose_name="avatar"
+ ),
),
]
diff --git a/cashonly/core/models.py b/cashonly/core/models.py
index 102e395..b5c0e83 100644
--- a/cashonly/core/models.py
+++ b/cashonly/core/models.py
@@ -1,38 +1,34 @@
-from django.conf import settings
from django.db import models
-from django.core.files import File
from django.contrib.auth.models import User
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
class Account(models.Model):
- user = models.OneToOneField(
- User
- )
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
card_number = models.CharField(
- verbose_name=_('card number'),
+ verbose_name=_("card number"),
max_length=32,
unique=True,
blank=True,
null=True,
)
pin = models.CharField(
- verbose_name=_('PIN'),
+ verbose_name=_("PIN"),
max_length=32,
blank=True,
)
daily_digest = models.BooleanField(
- verbose_name=_('daily digest'),
+ verbose_name=_("daily digest"),
default=True,
)
credit = models.DecimalField(
- verbose_name=_('credit'),
+ verbose_name=_("credit"),
max_digits=5,
decimal_places=2,
default=0,
)
debit_limit = models.DecimalField(
- verbose_name=_('debit limit'),
+ verbose_name=_("debit limit"),
max_digits=5,
decimal_places=2,
default=None,
@@ -40,8 +36,8 @@ class Account(models.Model):
null=True,
)
avatar = models.ImageField(
- verbose_name=_('avatar'),
- upload_to='avatars',
+ verbose_name=_("avatar"),
+ upload_to="avatars",
blank=True,
null=True,
)
@@ -50,60 +46,61 @@ class Account(models.Model):
return self.user.username
class Meta:
- verbose_name = _('account')
- verbose_name_plural = _('accounts')
+ verbose_name = _("account")
+ verbose_name_plural = _("accounts")
class ProductCategory(models.Model):
name = models.CharField(
- verbose_name=_('name'),
+ verbose_name=_("name"),
max_length=32,
unique=True,
)
comment = models.CharField(
- verbose_name=_('comment'),
+ verbose_name=_("comment"),
max_length=128,
blank=True,
)
def __str__(self):
- return '%s (%s)' % (self.name, self.comment)
+ return "%s (%s)" % (self.name, self.comment)
class Meta:
- verbose_name = _('product category')
- verbose_name_plural = _('product categories')
+ verbose_name = _("product category")
+ verbose_name_plural = _("product categories")
class Product(models.Model):
name = models.CharField(
- verbose_name=_('name'),
+ verbose_name=_("name"),
max_length=32,
unique=True,
)
price = models.DecimalField(
- verbose_name=_('price'),
+ verbose_name=_("price"),
max_digits=5,
decimal_places=2,
)
active = models.BooleanField(
- verbose_name=_('active'),
+ verbose_name=_("active"),
default=True,
)
category = models.ForeignKey(
ProductCategory,
- verbose_name=_('category'),
+ on_delete=models.CASCADE,
+ verbose_name=_("category"),
blank=True,
null=True,
)
image = models.ImageField(
- verbose_name=_('image'),
- upload_to='products',
+ verbose_name=_("image"),
+ upload_to="products",
blank=True,
null=True,
)
image_thumbnail = models.ImageField(
- verbose_name=_('image'),
- upload_to='products_thumb',
+ verbose_name=_("image"),
+ upload_to="products_thumb",
blank=True,
null=True,
)
@@ -112,88 +109,90 @@ class Product(models.Model):
return self.name
class Meta:
- verbose_name = _('product')
- verbose_name_plural = _('products')
+ verbose_name = _("product")
+ verbose_name_plural = _("products")
class ProductBarcode(models.Model):
barcode = models.CharField(
- verbose_name=_('barcode'),
+ verbose_name=_("barcode"),
max_length=32,
unique=True,
)
comment = models.CharField(
- verbose_name=_('comment'),
+ verbose_name=_("comment"),
max_length=128,
blank=True,
)
product = models.ForeignKey(
Product,
- verbose_name=_('product'),
+ on_delete=models.CASCADE,
+ verbose_name=_("product"),
)
def __unicode__(self):
return self.barcode
class Meta:
- verbose_name = _('barcode')
- verbose_name_plural = _('barcodes')
+ verbose_name = _("barcode")
+ verbose_name_plural = _("barcodes")
class Transaction(models.Model):
account = models.ForeignKey(
Account,
- verbose_name=_('account'),
+ on_delete=models.CASCADE,
+ verbose_name=_("account"),
)
timestamp = models.DateTimeField(
- verbose_name=_('timestamp'),
+ verbose_name=_("timestamp"),
auto_now_add=True,
)
subject = models.CharField(
- verbose_name=_('subject'),
+ verbose_name=_("subject"),
max_length=32,
)
description = models.TextField(
- verbose_name=_('description'),
+ verbose_name=_("description"),
)
amount = models.DecimalField(
- verbose_name=_('amount'),
+ verbose_name=_("amount"),
max_digits=5,
decimal_places=2,
)
class Meta:
- verbose_name = _('transaction')
- verbose_name_plural = _('transactions')
+ verbose_name = _("transaction")
+ verbose_name_plural = _("transactions")
class SalesLogEntry(models.Model):
account = models.ForeignKey(
Account,
- verbose_name=_('account'),
+ on_delete=models.CASCADE,
+ verbose_name=_("account"),
)
product = models.ForeignKey(
Product,
- verbose_name=_('product'),
+ on_delete=models.CASCADE,
+ verbose_name=_("product"),
)
count = models.IntegerField(
- verbose_name=_('count'),
+ verbose_name=_("count"),
)
unit_price = models.DecimalField(
- verbose_name=_('unit price'),
+ verbose_name=_("unit price"),
max_digits=5,
decimal_places=2,
)
timestamp = models.DateTimeField(
- verbose_name=_('timestamp'),
+ verbose_name=_("timestamp"),
auto_now_add=True,
)
def __unicode__(self):
- return '%dx %s - %s' % (self.count, self.product, self.account)
+ return "%dx %s - %s" % (self.count, self.product, self.account)
class Meta:
- verbose_name = _('sales log entry')
- verbose_name_plural = _('sales log entries')
-
-
+ verbose_name = _("sales log entry")
+ verbose_name_plural = _("sales log entries")
diff --git a/cashonly/core/services.py b/cashonly/core/services.py
index 945038f..60cc769 100644
--- a/cashonly/core/services.py
+++ b/cashonly/core/services.py
@@ -1,7 +1,7 @@
from cashonly.core.models import Account, Transaction, SalesLogEntry, Product
from django.core.files import File
from django.contrib.auth.models import User
-from django.utils.translation import ugettext_noop
+from django.utils.translation import gettext_noop
from django.db.models.signals import pre_save, post_save, pre_delete
from django.dispatch import receiver
from django.db import transaction
@@ -20,51 +20,56 @@ class AccountManager:
self.account.credit = self.account.credit + amount
self.account.save()
- Transaction.objects.create(account=self.account, subject=subject,
- amount=amount, description=description)
+ Transaction.objects.create(
+ account=self.account,
+ subject=subject,
+ amount=amount,
+ description=description,
+ )
def change_credit(self, amount, operator, comment=None):
if amount > 0:
- subject = ugettext_noop('Deposit')
+ subject = gettext_noop("Deposit")
elif amount < 0:
- subject = ugettext_noop('Payout')
+ subject = gettext_noop("Payout")
else:
- raise ValueError('Amount must not be zero.')
+ raise ValueError("Amount must not be zero.")
- desc = ugettext_noop('Authorized by %(first)s %(last)s') % \
- {'first': operator.first_name, 'last': operator.last_name}
+ desc = gettext_noop("Authorized by %(first)s %(last)s") % {
+ "first": operator.first_name,
+ "last": operator.last_name,
+ }
if comment is not None and len(comment) > 0:
- desc += ' (%s)' % (comment)
+ desc += " (%s)" % (comment)
self.add_transaction(amount, subject, desc)
@transaction.atomic
def buy_products(self, products):
if min(products.values()) <= 0:
- raise ValueError('Non-positive amount in products dict.')
+ raise ValueError("Non-positive amount in products dict.")
- total_value = sum(map(lambda p: p.price * products[p],
- products.keys()))
+ total_value = sum(map(lambda p: p.price * products[p], products.keys()))
if self.account.debit_limit is not None:
debit_limit = self.account.debit_limit
else:
debit_limit = settings.CASHONLY_DEFAULT_DEBIT_LIMIT
if self.account.credit - total_value >= debit_limit * -1:
- desc = ''
+ desc = ""
for product in products.keys():
if not product.active:
- raise ValueError('Trying to buy a disabled product.')
+ raise ValueError("Trying to buy a disabled product.")
amount = products[product]
- desc += '%d x %s\n' % (amount, product.name)
+ desc += "%d x %s\n" % (amount, product.name)
SalesLogEntry.objects.create(
account=self.account,
product=product,
count=amount,
- unit_price=product.price
+ unit_price=product.price,
)
- self.add_transaction(-total_value, ugettext_noop('Purchase'), desc)
+ self.add_transaction(-total_value, gettext_noop("Purchase"), desc)
return True
else:
return False
@@ -77,7 +82,7 @@ class AccountManager:
self.account.save()
def clear_pin(self):
- self.set_pin('')
+ self.set_pin("")
def check_pin(self, pin):
return self.account.pin == pin
@@ -95,8 +100,8 @@ def logentry_pre_delete_handler(sender, instance, **kwargs):
accmgr = AccountManager(instance.account)
accmgr.add_transaction(
instance.unit_price * instance.count,
- ugettext_noop('Cancellation'),
- '%d x %s' % (instance.count, instance.product.name)
+ gettext_noop("Cancellation"),
+ "%d x %s" % (instance.count, instance.product.name),
)
@@ -106,14 +111,12 @@ def product_post_save_handler(sender, instance, **kwargs):
img = instance.image
if img:
scaledFile = io.StringIO()
- img.open(mode='r')
+ img.open(mode="r")
with img:
scaled = PIL.Image.open(img)
- thumbnail_size = getattr(settings, 'THUMBNAIL_SIZE', (150, 150))
+ thumbnail_size = getattr(settings, "THUMBNAIL_SIZE", (150, 150))
scaled.thumbnail(thumbnail_size, PIL.Image.ANTIALIAS)
- scaled.save(scaledFile, 'PNG')
+ scaled.save(scaledFile, "PNG")
scaledFile.seek(0)
instance.image_thumbnail.save(img.url, File(scaledFile), save=False)
-
-
diff --git a/cashonly/web/__init__.py b/cashonly/web/__init__.py
index 8721777..bc3dc43 100644
--- a/cashonly/web/__init__.py
+++ b/cashonly/web/__init__.py
@@ -2,8 +2,8 @@ from django.apps import AppConfig
class CashonlyWebAppConfig(AppConfig):
- name = 'cashonly.web'
- label = 'cashonly_web'
+ name = "cashonly.web"
+ label = "cashonly_web"
-default_app_config = 'cashonly.web.CashonlyWebAppConfig'
+default_app_config = "cashonly.web.CashonlyWebAppConfig"
diff --git a/cashonly/web/templates/cashonly/web/base.html b/cashonly/web/templates/cashonly/web/base.html
index 26265eb..8a3deff 100644
--- a/cashonly/web/templates/cashonly/web/base.html
+++ b/cashonly/web/templates/cashonly/web/base.html
@@ -1,4 +1,4 @@
-{% load staticfiles %}
+{% load static %}
{% load i18n %}
@@ -9,151 +9,148 @@
Kassensystem
-
-
+
+
-
-
- {% endblock %}
+
+ {% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% block content %}
-
-
{% blocktrans with firstname=user.first_name %}Welcome, {{ firstname }}!{% endblocktrans %}
-
-
-
-
-
- {% trans "Account information" %}
-
-
-
- - {% trans "Username:" %}
- - {{ user.username }}
- - {% trans "Full name:" %}
- - {{ user.first_name }} {{ user.last_name }}
- - {% trans "E-Mail address:" %}
- - {{ user.email }}
- - {% trans "Credit:" %}
- - {{ user.account.credit|floatformat:2 }}€
-
-
-
-
-
-
-
- {% trans "Last purchased products" %}
-
-
- {% include "cashonly/web/includes/product_list.html" with products=latest_purchases class="col-xs-6 col-md-4" only %}
-
-
-
-
-
-
- {% trans "Your transactions in the last 12 hours" %}
-
- {% if latest_transactions %}
- {% include "cashonly/web/includes/transaction_list.html" with transactions=latest_transactions only %}
- {% else %}
-
- {% blocktrans %}There where no transactions in your account in the last 12 hours.{% endblocktrans %}
-
- {% endif %}
-
-
-
-
- {% endblock %}
-
-
-
-
-
-
-
-
- cashonly v{{version_number}}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block content %}
+
+
{% blocktrans with firstname=user.first_name %}Welcome, {{ firstname }}!{% endblocktrans %}
+
+
+
+
+
+ {% trans "Account information" %}
+
+
+
+ - {% trans "Username:" %}
+ - {{ user.username }}
+ - {% trans "Full name:" %}
+ - {{ user.first_name }} {{ user.last_name }}
+ - {% trans "E-Mail address:" %}
+ - {{ user.email }}
+ - {% trans "Credit:" %}
+ - {{ user.account.credit|floatformat:2 }}€
+
+
+
+
+
+
+
+ {% trans "Last purchased products" %}
+
+
+ {% include "cashonly/web/includes/product_list.html" with products=latest_purchases class="col-xs-6 col-md-4" only %}
+
+
+
+
+
+
+ {% trans "Your transactions in the last 12 hours" %}
+
+ {% if latest_transactions %}
+ {% include "cashonly/web/includes/transaction_list.html" with transactions=latest_transactions only %}
+ {% else %}
+
+ {% blocktrans %}There where no transactions in your account in the last 12 hours.{% endblocktrans %}
+
+ {% endif %}
+
+
+
+
+ {% endblock %}
+
+
+
+
+
+
+
+
+ cashonly v{{version_number}}
+
+
+
+
+
+