migrate to django 4 #1

Open
informaniac wants to merge 4 commits from migrate/django4 into master
  1. 9
      .gitignore
  2. 6
      cashonly/core/__init__.py
  3. 108
      cashonly/core/admin.py
  4. 5
      cashonly/core/apps.py
  5. 4
      cashonly/core/auth.py
  6. 4
      cashonly/core/formfields.py
  7. 89
      cashonly/core/management/commands/dailydigest.py
  8. 31
      cashonly/core/management/commands/debtreminder.py
  9. 270
      cashonly/core/migrations/0001_initial.py
  10. 10
      cashonly/core/migrations/0002_account_debit_limit.py
  11. 15
      cashonly/core/migrations/0003_debit_limit_null_default.py
  12. 10
      cashonly/core/migrations/0004_account_avatar.py
  13. 103
      cashonly/core/models.py
  14. 53
      cashonly/core/services.py
  15. 6
      cashonly/web/__init__.py
  16. 2
      cashonly/web/templates/cashonly/web/base.html
  17. 2
      cashonly/web/templates/cashonly/web/login.html
  18. 2
      cashonly/web/templates/cashonly/web/usersettings.html
  19. 61
      cashonly/web/urls.py
  20. 165
      cashonly/web/views.py
  21. 4
      config/copysecret.py
  22. 92
      config/settings.py
  23. 14
      config/urls.py
  24. 2
      requirements.txt

9
.gitignore vendored

@ -1,4 +1,11 @@ @@ -1,4 +1,11 @@
venv/
online/
.idea/
.DS_Store
.DS_Store
# local files
server/
cashonly/web/static/bootstrap/**/*
cashonly/web/static/jquery.min.js
db.sqlite3
manage.py

6
cashonly/core/__init__.py

@ -2,11 +2,11 @@ from django.apps import AppConfig @@ -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"

108
cashonly/core/admin.py

@ -3,26 +3,23 @@ from cashonly.core.models import * @@ -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): @@ -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): @@ -55,26 +62,28 @@ class AccountAdmin(admin.ModelAdmin):
return False
def transaction_link(self, account):
return '<a href="%s?account__id__exact=%d">%s</a>' % \
(reverse("admin:cashonly_core_transaction_changelist"), account.id,
_('Transactions'))
return '<a href="%s?account__id__exact=%d">%s</a>' % (
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): @@ -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): @@ -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)

5
cashonly/core/apps.py

@ -2,5 +2,6 @@ from django.apps import AppConfig @@ -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"

4
cashonly/core/auth.py

@ -6,9 +6,9 @@ from cashonly.core.services import AccountManager @@ -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:

4
cashonly/core/formfields.py

@ -1,6 +1,6 @@ @@ -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): @@ -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

89
cashonly/core/management/commands/dailydigest.py

@ -1,75 +1,90 @@ @@ -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))

31
cashonly/core/management/commands/debtreminder.py

@ -1,4 +1,3 @@ @@ -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 @@ -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))

270
cashonly/core/migrations/0001_initial.py

@ -17,93 +17,259 @@ class Migration(migrations.Migration): @@ -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",
),
),
]

10
cashonly/core/migrations/0002_account_debit_limit.py

@ -8,13 +8,15 @@ from django.db import migrations, models @@ -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"
),
),
]

15
cashonly/core/migrations/0003_debit_limit_null_default.py

@ -8,13 +8,20 @@ from django.db import migrations, models @@ -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",
),
),
]

10
cashonly/core/migrations/0004_account_avatar.py

@ -8,13 +8,15 @@ from django.db import migrations, models @@ -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"
),
),
]

103
cashonly/core/models.py

@ -1,38 +1,34 @@ @@ -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): @@ -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): @@ -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): @@ -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")

53
cashonly/core/services.py

@ -1,7 +1,7 @@ @@ -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: @@ -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: @@ -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): @@ -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): @@ -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)

6
cashonly/web/__init__.py

@ -2,8 +2,8 @@ from django.apps import AppConfig @@ -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"

2
cashonly/web/templates/cashonly/web/base.html

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
{% load staticfiles %}
{% load static %}
{% load i18n %}
<!DOCTYPE html>
<html lang="en">

2
cashonly/web/templates/cashonly/web/login.html

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
{% load i18n %}
{% load staticfiles %}
{% load static %}
<!DOCTYPE html>
<html lang="en">

2
cashonly/web/templates/cashonly/web/usersettings.html

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
{% extends "cashonly/web/base.html" %}
{% load i18n %}
{% load bootstrap %}
{% load staticfiles %}
{% load static %}
{% block head %}
{{ block.super }}

61
cashonly/web/urls.py

@ -1,33 +1,36 @@ @@ -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<pk>\d+)/$', views.ProductView.as_view(),
name='product'),
url(r'transactions/$', views.transactions, {'detailed': False, 'page': 1},
name='transactions'),
url(r'transactions/(?P<page>\d+)/$', views.transactions,
{'detailed': False}, name='transactions'),
url(r'transactions/(?P<page>\d+)/detailed/$', views.transactions,
{'detailed': True}, name='transactions_detailed'),
url(r'products/((?P<category_id>\d+)/)?$', views.products,
name='products'),
url(r'buy/(?P<product_id>\d+)/$', views.buy, name='buy'),
url(r'buy/(?P<product_id>\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<submit>\w+))?/$', views.usersettings,
name='usersettings'),
re_path(r"^$", views.overview, name="overview"),
re_path(r"^product/(?P<pk>\d+)/$", views.ProductView.as_view(), name="product"),
re_path(
r"transactions/$",
views.transactions,
{"detailed": False, "page": 1},
name="transactions",
),
re_path(
r"transactions/(?P<page>\d+)/$",
views.transactions,
{"detailed": False},
name="transactions",
),
re_path(
r"transactions/(?P<page>\d+)/detailed/$",
views.transactions,
{"detailed": True},
name="transactions_detailed",
),
re_path(r"products/((?P<category_id>\d+)/)?$", views.products, name="products"),
re_path(r"buy/(?P<product_id>\d+)/$", views.buy, name="buy"),
re_path(
r"buy/(?P<product_id>\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<submit>\w+))?/$", views.usersettings, name="usersettings"),
]

165
cashonly/web/views.py

@ -3,17 +3,17 @@ from django import forms @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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): @@ -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,
},
)

4
config/copysecret.py

@ -6,7 +6,7 @@ for line in fp: @@ -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): @@ -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)

92
config/settings.py

@ -16,68 +16,68 @@ import os @@ -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 = { @@ -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 @@ -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

14
config/urls.py

@ -13,15 +13,19 @@ Including another URLconf @@ -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)

2
requirements.txt

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
Django==1.11.20
Django==4.1.3
django-bootstrap-form==3.4
psycopg2-binary
Pillow

Loading…
Cancel
Save