diff --git a/.gitignore b/.gitignore
index 1857c4f..3b755c2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,11 @@
venv/
online/
.idea/
-.DS_Store
\ No newline at end of file
+.DS_Store
+
+# local files
+server/
+cashonly/web/static/bootstrap/**/*
+cashonly/web/static/jquery.min.js
+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..6480c09 100644
--- a/cashonly/core/apps.py
+++ b/cashonly/core/apps.py
@@ -2,5 +2,6 @@ from django.apps import AppConfig
class CashonlyCoreConfig(AppConfig):
- name = 'cashonly.core'
- label = 'cashonly_core'
+ name = "cashonly.core"
+ label = "cashonly_core"
+ default_auto_field = "django.db.models.AutoField"
\ No newline at end of file
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..598d4fd 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 %}
diff --git a/cashonly/web/templates/cashonly/web/login.html b/cashonly/web/templates/cashonly/web/login.html
index 41117ce..21c7a91 100644
--- a/cashonly/web/templates/cashonly/web/login.html
+++ b/cashonly/web/templates/cashonly/web/login.html
@@ -1,5 +1,5 @@
{% load i18n %}
-{% load staticfiles %}
+{% load static %}
diff --git a/cashonly/web/templates/cashonly/web/usersettings.html b/cashonly/web/templates/cashonly/web/usersettings.html
index cb625dd..f537b24 100644
--- a/cashonly/web/templates/cashonly/web/usersettings.html
+++ b/cashonly/web/templates/cashonly/web/usersettings.html
@@ -1,7 +1,7 @@
{% extends "cashonly/web/base.html" %}
{% load i18n %}
{% load bootstrap %}
-{% load staticfiles %}
+{% load static %}
{% block head %}
{{ block.super }}
diff --git a/cashonly/web/urls.py b/cashonly/web/urls.py
index 7d97a67..3143c74 100644
--- a/cashonly/web/urls.py
+++ b/cashonly/web/urls.py
@@ -1,33 +1,36 @@
-from django.conf.urls import url
+from django.urls import re_path
from cashonly.web import views
urlpatterns = [
- url(r'^$', views.overview, name='overview'),
-
- url(r'^product/(?P\d+)/$', views.ProductView.as_view(),
- name='product'),
-
- url(r'transactions/$', views.transactions, {'detailed': False, 'page': 1},
- name='transactions'),
-
- url(r'transactions/(?P\d+)/$', views.transactions,
- {'detailed': False}, name='transactions'),
-
- url(r'transactions/(?P\d+)/detailed/$', views.transactions,
- {'detailed': True}, name='transactions_detailed'),
-
- url(r'products/((?P\d+)/)?$', views.products,
- name='products'),
-
- url(r'buy/(?P\d+)/$', views.buy, name='buy'),
-
- url(r'buy/(?P\d+)/really/$', views.buy,
- {'confirm': True}, name='buy_really'),
-
- url(r'buy/thanks/$', views.buy_thanks, name='buy_thanks'),
-
- url(r'buy/error/$', views.buy_error, name='buy_error'),
-
- url(r'usersettings(/(?P\w+))?/$', views.usersettings,
- name='usersettings'),
+ re_path(r"^$", views.overview, name="overview"),
+ re_path(r"^product/(?P\d+)/$", views.ProductView.as_view(), name="product"),
+ re_path(
+ r"transactions/$",
+ views.transactions,
+ {"detailed": False, "page": 1},
+ name="transactions",
+ ),
+ re_path(
+ r"transactions/(?P\d+)/$",
+ views.transactions,
+ {"detailed": False},
+ name="transactions",
+ ),
+ re_path(
+ r"transactions/(?P\d+)/detailed/$",
+ views.transactions,
+ {"detailed": True},
+ name="transactions_detailed",
+ ),
+ re_path(r"products/((?P\d+)/)?$", views.products, name="products"),
+ re_path(r"buy/(?P\d+)/$", views.buy, name="buy"),
+ re_path(
+ r"buy/(?P\d+)/really/$",
+ views.buy,
+ {"confirm": True},
+ name="buy_really",
+ ),
+ re_path(r"buy/thanks/$", views.buy_thanks, name="buy_thanks"),
+ re_path(r"buy/error/$", views.buy_error, name="buy_error"),
+ re_path(r"usersettings(/(?P\w+))?/$", views.usersettings, name="usersettings"),
]
diff --git a/cashonly/web/views.py b/cashonly/web/views.py
index 6b7d824..1f8e01a 100644
--- a/cashonly/web/views.py
+++ b/cashonly/web/views.py
@@ -3,17 +3,17 @@ from django import forms
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.core import paginator
-from django.contrib.auth.models import User
from cashonly.core.models import *
from cashonly.core.services import AccountManager
-from django.utils.translation import ugettext as _
-from django.utils.translation import ugettext_lazy
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
from django.db import IntegrityError
-#import cashonly.core.version
+
+# import cashonly.core.version
import datetime
-#def version_number_context_processor(request):
+# def version_number_context_processor(request):
# return {'version_number': cashonly.version.CASHONLY_VERSION}
@@ -22,17 +22,26 @@ def overview(request):
try:
a = request.user.account
except User.account.RelatedObjectDoesNotExist:
- return render(request, 'cashonly/web/index.html',
- {'latest_transactions': [], 'latest_purchases': []})
+ return render(
+ request,
+ "cashonly/web/index.html",
+ {"latest_transactions": [], "latest_purchases": []},
+ )
time = datetime.datetime.now() - datetime.timedelta(hours=12)
- transactions = Transaction.objects.filter(account=a) \
- .filter(timestamp__gte=time).order_by('-timestamp')
+ transactions = (
+ Transaction.objects.filter(account=a)
+ .filter(timestamp__gte=time)
+ .order_by("-timestamp")
+ )
# FIXME: distinct doesn't work as expected, so fetch 20 rows and hope that
# there are 3 distinct products
- purchases = Product.objects.filter(saleslogentry__account=a) \
- .order_by('-saleslogentry__timestamp').distinct()[:20]
+ purchases = (
+ Product.objects.filter(saleslogentry__account=a)
+ .order_by("-saleslogentry__timestamp")
+ .distinct()[:20]
+ )
products = []
# Find 3 products
@@ -43,9 +52,11 @@ def overview(request):
if len(products) == 3:
break
- return render(request, 'cashonly/web/index.html',
- {'latest_transactions': transactions,
- 'latest_purchases': products})
+ return render(
+ request,
+ "cashonly/web/index.html",
+ {"latest_transactions": transactions, "latest_purchases": products},
+ )
class ProductView(generic.DetailView):
@@ -55,7 +66,7 @@ class ProductView(generic.DetailView):
@login_required
def transactions(request, detailed, page):
a = request.user.account
- transactions = Transaction.objects.filter(account=a).order_by('-timestamp')
+ transactions = Transaction.objects.filter(account=a).order_by("-timestamp")
if page is None:
page = 1
@@ -66,9 +77,11 @@ def transactions(request, detailed, page):
except paginator.EmptyPage:
transaction_list = paginator.page(paginator.num_pages)
- return render(request, 'cashonly/web/transaction_list.html',
- {'transaction_list': transaction_list,
- 'detailed': detailed})
+ return render(
+ request,
+ "cashonly/web/transaction_list.html",
+ {"transaction_list": transaction_list, "detailed": detailed},
+ )
def products(request, category_id=None):
@@ -77,14 +90,15 @@ def products(request, category_id=None):
products = Product.objects.filter(active=True)
else:
category = get_object_or_404(ProductCategory, id=category_id)
- products = Product.objects.filter(active=True) \
- .filter(category=category)
+ products = Product.objects.filter(active=True).filter(category=category)
categories = ProductCategory.objects.all()
- return render(request, 'cashonly/web/product_list.html',
- {'product_list': products, 'category': category,
- 'categories': categories})
+ return render(
+ request,
+ "cashonly/web/product_list.html",
+ {"product_list": products, "category": category, "categories": categories},
+ )
@login_required
@@ -94,121 +108,134 @@ def buy(request, product_id, confirm=False):
if confirm:
accmgr = AccountManager(request.user.account)
if accmgr.buy_product(product, 1):
- return redirect('buy_thanks')
+ return redirect("buy_thanks")
else:
- return redirect('buy_error')
+ return redirect("buy_error")
else:
- return render(request, 'cashonly/web/buy_confirm.html',
- {'product': product})
+ return render(request, "cashonly/web/buy_confirm.html", {"product": product})
@login_required
def buy_thanks(request):
- return render(request, 'cashonly/web/buy_thanks.html')
+ return render(request, "cashonly/web/buy_thanks.html")
@login_required
def buy_error(request):
- return render(request, 'cashonly/web/buy_error.html')
+ return render(request, "cashonly/web/buy_error.html")
class UserSettingsForm(forms.Form):
- daily_digest = forms.BooleanField(required=False,
- label=ugettext_lazy('daily digest'))
+ daily_digest = forms.BooleanField(
+ required=False, label=gettext_lazy("daily digest")
+ )
class UserPinForm(forms.Form):
- pin = forms.CharField(max_length=32, widget=forms.PasswordInput,
- label=ugettext_lazy('PIN'), required=False)
- pin_confirm = forms.CharField(max_length=32, widget=forms.PasswordInput,
- label=ugettext_lazy('PIN (confirmation)'),
- required=False)
+ pin = forms.CharField(
+ max_length=32,
+ widget=forms.PasswordInput,
+ label=gettext_lazy("PIN"),
+ required=False,
+ )
+ pin_confirm = forms.CharField(
+ max_length=32,
+ widget=forms.PasswordInput,
+ label=gettext_lazy("PIN (confirmation)"),
+ required=False,
+ )
def clean(self):
cleaned_data = super(UserPinForm, self).clean()
- if 'pin' not in cleaned_data and 'pin_confirm' not in cleaned_data:
+ if "pin" not in cleaned_data and "pin_confirm" not in cleaned_data:
return cleaned_data
- if cleaned_data['pin'] != cleaned_data['pin_confirm']:
- raise forms.ValidationError(_('PINs do not match.'))
+ if cleaned_data["pin"] != cleaned_data["pin_confirm"]:
+ raise forms.ValidationError(_("PINs do not match."))
return cleaned_data
class UserAvatarForm(forms.Form):
- avatar = forms.ImageField(label=ugettext_lazy('avatar'), required=False)
+ avatar = forms.ImageField(label=gettext_lazy("avatar"), required=False)
class UserCardNumberForm(forms.Form):
- card_number = forms.CharField(max_length=32,
- label=ugettext_lazy('card number'),
- required=False)
+ card_number = forms.CharField(
+ max_length=32, label=gettext_lazy("card number"), required=False
+ )
@login_required
def usersettings(request, submit=None):
daily_digest = request.user.account.daily_digest
- settings_form = UserSettingsForm({'daily_digest': daily_digest})
+ settings_form = UserSettingsForm({"daily_digest": daily_digest})
pin_form = UserPinForm()
avatar_form = UserAvatarForm()
- card_number_form = UserCardNumberForm({
- 'card_number': request.user.account.card_number
- })
+ card_number_form = UserCardNumberForm(
+ {"card_number": request.user.account.card_number}
+ )
- if request.method == 'POST':
- if submit == 'pin':
+ if request.method == "POST":
+ if submit == "pin":
pin_form = UserPinForm(request.POST)
if pin_form.is_valid():
- pin = pin_form.cleaned_data['pin']
+ pin = pin_form.cleaned_data["pin"]
accmgr = AccountManager(request.user.account)
if pin is not None:
accmgr.set_pin(pin)
else:
accmgr.clear_pin()
- return render(request, 'cashonly/web/usersettings_saved.html')
+ return render(request, "cashonly/web/usersettings_saved.html")
- elif submit == 'settings':
+ elif submit == "settings":
settings_form = UserSettingsForm(request.POST)
if settings_form.is_valid():
- daily_digest = settings_form.cleaned_data['daily_digest']
+ daily_digest = settings_form.cleaned_data["daily_digest"]
request.user.account.daily_digest = daily_digest
request.user.account.save()
- return render(request, 'cashonly/web/usersettings_saved.html')
- elif submit == 'avatar':
+ return render(request, "cashonly/web/usersettings_saved.html")
+ elif submit == "avatar":
avatar_form = UserAvatarForm(request.POST, request.FILES)
if avatar_form.is_valid():
- if 'delete' in request.POST:
+ if "delete" in request.POST:
request.user.account.avatar = None
else:
- request.user.account.avatar = \
- avatar_form.cleaned_data['avatar']
+ request.user.account.avatar = avatar_form.cleaned_data["avatar"]
request.user.account.save()
- return render(request, 'cashonly/web/usersettings_saved.html')
+ return render(request, "cashonly/web/usersettings_saved.html")
else:
# TODO handle upload error (e.g. wrong mime type?)
pass
- elif submit == 'card_number':
+ elif submit == "card_number":
card_number_form = UserCardNumberForm(request.POST)
# TODO validate card number
if card_number_form.is_valid():
- request.user.account.card_number = \
- card_number_form.cleaned_data['card_number']
+ request.user.account.card_number = card_number_form.cleaned_data[
+ "card_number"
+ ]
try:
request.user.account.save()
except IntegrityError:
return render(
request,
- 'cashonly/web/usersettings_error.html',
- {'msg': _('Card number is already in use.')}
+ "cashonly/web/usersettings_error.html",
+ {"msg": _("Card number is already in use.")},
)
- return render(request, 'cashonly/web/usersettings_saved.html')
+ return render(request, "cashonly/web/usersettings_saved.html")
else:
pass
# TODO handle error (invalid card number)
- return render(request, 'cashonly/web/usersettings.html',
- {'settings_form': settings_form, 'pin_form': pin_form,
- 'avatar_form': avatar_form,
- 'card_number_form': card_number_form})
+ return render(
+ request,
+ "cashonly/web/usersettings.html",
+ {
+ "settings_form": settings_form,
+ "pin_form": pin_form,
+ "avatar_form": avatar_form,
+ "card_number_form": card_number_form,
+ },
+ )
diff --git a/config/copysecret.py b/config/copysecret.py
index 50d5da3..27acf9d 100644
--- a/config/copysecret.py
+++ b/config/copysecret.py
@@ -6,7 +6,7 @@ for line in fp:
break
fp.close()
-with open('/django/config/settings.py', 'r') as file:
+with open("/django/config/settings.py", "r") as file:
data = file.readlines()
for i, line in enumerate(data):
@@ -14,5 +14,5 @@ for i, line in enumerate(data):
data[i] = "{} = {}".format("SECRET_KEY", key)
break
-with open('/django/config/settings.py', 'w') as file:
+with open("/django/config/settings.py", "w") as file:
file.writelines(data)
diff --git a/config/settings.py b/config/settings.py
index e3d2369..01b4691 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -16,68 +16,68 @@ import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = '&h!kbrmk)la2m%+%-qmdj1&ifg^rj3$o*)sos2dedw$$b4_lp$'
+SECRET_KEY = "&h!kbrmk)la2m%+%-qmdj1&ifg^rj3$o*)sos2dedw$$b4_lp$"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = ['*']
+ALLOWED_HOSTS = ["*"]
# Application definition
INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'cashonly.core',
- 'cashonly.web',
- 'bootstrapform',
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "cashonly.core",
+ "cashonly.web",
+ "bootstrapform",
]
MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
]
-ROOT_URLCONF = 'server.urls'
+ROOT_URLCONF = "server.urls"
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
],
},
},
]
-WSGI_APPLICATION = 'server.wsgi.application'
+WSGI_APPLICATION = "server.wsgi.application"
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.postgresql',
- 'NAME': 'postgres',
- 'USER': 'postgres',
- 'PASSWORD': os.environ.get("POSTGRES_PASSWORD"),
- 'HOST': os.environ.get("POSTGRES_HOST"),
- 'PORT': 5432, # default
+ "default": {
+ "ENGINE": "django.db.backends.postgresql",
+ "NAME": "postgres",
+ "USER": "postgres",
+ "PASSWORD": os.environ.get("POSTGRES_PASSWORD"),
+ "HOST": os.environ.get("POSTGRES_HOST"),
+ "PORT": 5432, # default
}
}
@@ -86,27 +86,27 @@ DATABASES = {
AUTH_PASSWORD_VALIDATORS = [
{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
-LOGIN_URL = '/login/'
-LOGIN_REDIRECT_URL = '/'
+LOGIN_URL = "/login/"
+LOGIN_REDIRECT_URL = "/"
# Internationalization
# https://docs.djangoproject.com/en/1.11/topics/i18n/
-LANGUAGE_CODE = 'de-de'
-TIME_ZONE = 'UTC'
+LANGUAGE_CODE = "de-de"
+TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
@@ -114,8 +114,8 @@ USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
-STATIC_URL = '/static/'
-STATICFILES_DIRS = ['/django/static_thirdparty']
+STATIC_URL = "/static/"
+STATICFILES_DIRS = ["/django/static_thirdparty"]
# Cashonly Stuff
# https://git.blinkenbunt.org/cashonly/cashonly
diff --git a/config/urls.py b/config/urls.py
index 5434817..c955eb4 100644
--- a/config/urls.py
+++ b/config/urls.py
@@ -13,15 +13,19 @@ Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
-from django.conf.urls import url, include
+from django.urls import re_path, include
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
- url(r'^admin/', admin.site.urls),
- url(r'', include('cashonly.web.urls')),
- url(r'^login/$', auth_views.LoginView.as_view(template_name='cashonly/web/login.html'), name='login'),
- url(r'^logout/$', auth_views.logout_then_login, name='logout_then_login'),
+ re_path(r"^admin/", admin.site.urls),
+ re_path(r"", include("cashonly.web.urls")),
+ re_path(
+ r"^login/$",
+ auth_views.LoginView.as_view(template_name="cashonly/web/login.html"),
+ name="login",
+ ),
+ re_path(r"^logout/$", auth_views.logout_then_login, name="logout_then_login"),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
diff --git a/requirements.txt b/requirements.txt
index 7e1a715..af43bcd 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
-Django==1.11.20
+Django==4.1.3
django-bootstrap-form==3.4
psycopg2-binary
Pillow