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}} +

+
+
+
+
+ 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