Browse Source

pep8

master
Fr3deric 7 years ago
parent
commit
e1598090f7
  1. 232
      cashonly/admin.py
  2. 27
      cashonly/auth.py
  3. 11
      cashonly/formfields.py
  4. 83
      cashonly/management/commands/dailydigest.py
  5. 33
      cashonly/management/commands/debtreminder.py
  6. 336
      cashonly/models.py
  7. 33
      cashonly/urls.py
  8. 4
      cashonly/version.py
  9. 49
      cashonly/views.py

232
cashonly/admin.py

@ -8,141 +8,147 @@ from django.utils.translation import ugettext as _ @@ -8,141 +8,147 @@ from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from django.utils.translation import ugettext_noop
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=ugettext_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=ugettext_lazy('comment'))
pin_change = DigitField(min_length=4, required=False,
label=ugettext_lazy('PIN'))
#pin_change = forms.RegexField(regex='^\d{4,}$', required=False,
# label=ugettext_lazy('PIN'))
pin_change = DigitField(min_length=4, required=False,
label=ugettext_lazy('PIN'))
pin_empty = forms.BooleanField(required=False,
label=ugettext_lazy('clear PIN'))
pin_empty = forms.BooleanField(required=False,
label=ugettext_lazy('clear PIN'))
class Meta:
model = Account
class Meta:
model = Account
# Include all fields (omitting this causes a RemovedInDjango18Warning)
exclude = []
# Include all fields (omitting this causes a RemovedInDjango18Warning)
exclude = []
class AccountAdmin(admin.ModelAdmin):
list_display = ('user', 'card_number', 'credit', 'transaction_link')
form = AccountForm
readonly_fields = ('user', 'credit',)
fieldsets = (
(None, {
'fields': ('user', 'card_number', 'credit'),
}),
(ugettext_lazy('credit change'), {
'fields': ('credit_change', 'credit_change_comment'),
}),
(ugettext_lazy('change PIN'), {
'fields': ('pin_change', 'pin_empty'),
}),
)
# Disable manual creation of accounts.
# Accounts are create after user creation automatically.
def has_add_permission(self, request):
return False
def transaction_link(self, account):
return '<a href="%s?account__id__exact=%d">%s</a>' % \
(reverse("admin:cashonly_transaction_changelist"), account.id,
_('Transactions'))
transaction_link.short_description = ugettext_lazy('Transaction link')
transaction_link.allow_tags = True
def save_model(self, request, obj, form, change):
pin = form.cleaned_data['pin_change']
pin_empty = form.cleaned_data['pin_empty']
if pin_empty:
obj.clear_pin()
else:
if pin is not None and len(pin) != 0:
obj.set_pin(pin)
PAYOUT_SUBJECT = ugettext_noop('Payout')
DEPOSIT_SUBJECT = ugettext_noop('Deposit')
DESCRIPTION = ugettext_noop('Authorized by %(first)s %(last)s')
amount = form.cleaned_data['credit_change']
comment = form.cleaned_data['credit_change_comment']
if amount is not None and amount != 0:
if amount > 0:
subject = DEPOSIT_SUBJECT
else:
subject = PAYOUT_SUBJECT
desc = DESCRIPTION % {'first': request.user.first_name,
'last': request.user.last_name}
if comment is not None and len(comment) > 0:
desc += ' (%s)' % (comment)
obj.change_credit(amount, subject, desc)
# Make sure the object is saved in any case
obj.save()
list_display = ('user', 'card_number', 'credit', 'transaction_link')
form = AccountForm
readonly_fields = ('user', 'credit',)
fieldsets = (
(None, {
'fields': ('user', 'card_number', 'credit'),
}),
(ugettext_lazy('credit change'), {
'fields': ('credit_change', 'credit_change_comment'),
}),
(ugettext_lazy('change PIN'), {
'fields': ('pin_change', 'pin_empty'),
}),
)
# Disable manual creation of accounts.
# Accounts are create after user creation automatically.
def has_add_permission(self, request):
return False
def transaction_link(self, account):
return '<a href="%s?account__id__exact=%d">%s</a>' % \
(reverse("admin:cashonly_transaction_changelist"), account.id,
_('Transactions'))
transaction_link.short_description = ugettext_lazy('Transaction link')
transaction_link.allow_tags = True
def save_model(self, request, obj, form, change):
pin = form.cleaned_data['pin_change']
pin_empty = form.cleaned_data['pin_empty']
if pin_empty:
obj.clear_pin()
else:
if pin is not None and len(pin) != 0:
obj.set_pin(pin)
PAYOUT_SUBJECT = ugettext_noop('Payout')
DEPOSIT_SUBJECT = ugettext_noop('Deposit')
DESCRIPTION = ugettext_noop('Authorized by %(first)s %(last)s')
amount = form.cleaned_data['credit_change']
comment = form.cleaned_data['credit_change_comment']
if amount is not None and amount != 0:
if amount > 0:
subject = DEPOSIT_SUBJECT
else:
subject = PAYOUT_SUBJECT
desc = DESCRIPTION % {'first': request.user.first_name,
'last': request.user.last_name}
if comment is not None and len(comment) > 0:
desc += ' (%s)' % (comment)
obj.change_credit(amount, subject, desc)
# Make sure the object is saved in any case
obj.save()
class ProductBarcodeInline(admin.TabularInline):
model = ProductBarcode
extra = 1
model = ProductBarcode
extra = 1
class ProductAdmin(admin.ModelAdmin):
list_display = ('name', 'category', 'price', 'active')
list_filter = ['category', 'active']
ordering = ('-active', 'name')
inlines = [ProductBarcodeInline]
fields = ('name', 'price', 'active', 'category', 'image')
list_display = ('name', 'category', 'price', 'active')
list_filter = ['category', 'active']
ordering = ('-active', 'name')
inlines = [ProductBarcodeInline]
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']
# Make sales log entries completely read-only
readonly_fields = map(lambda f: f.name, SalesLogEntry._meta.fields)
list_display = ('account', 'timestamp', 'product', 'count', 'unit_price')
list_filter = ['account', 'timestamp', 'product']
# Make sales log entries completely read-only
readonly_fields = map(lambda f: f.name, SalesLogEntry._meta.fields)
class TransactionAdmin(admin.ModelAdmin):
list_display = ('account', 'timestamp', 'subject', 'description', 'amount')
list_filter = ['account', 'timestamp', 'subject']
# Disable mass deletion in the overview page
actions = None
date_hierarchy = 'timestamp'
# Disable tampering with the transactions completely.
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
if obj is None:
return True
return False
def has_delete_permission(self, request, obj=None):
return False
# 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, )
return super(TransactionAdmin, self).changelist_view(request,
extra_context=None)
list_display = ('account', 'timestamp', 'subject', 'description', 'amount')
list_filter = ['account', 'timestamp', 'subject']
# Disable mass deletion in the overview page
actions = None
date_hierarchy = 'timestamp'
# Disable tampering with the transactions completely.
def has_add_permission(self, request):
return False
def has_change_permission(self, request, obj=None):
if obj is None:
return True
return False
def has_delete_permission(self, request, obj=None):
return False
# 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, )
return super(TransactionAdmin, self).changelist_view(
request,
extra_context=None,
)
admin.site.register(Account, AccountAdmin)
admin.site.register(Product, ProductAdmin)
#admin.site.register(ProductBarcode)
admin.site.register(ProductCategory, ProductCategoryAdmin)
admin.site.register(Transaction, TransactionAdmin)
admin.site.register(SalesLogEntry, SalesLogEntryAdmin)

27
cashonly/auth.py

@ -1,21 +1,22 @@ @@ -1,21 +1,22 @@
from django.contrib.auth.models import User
from cashonly.models import Account
class CashBackend(object):
def authenticate(self, card_number=None, pin=None):
if not card_number.isdigit():
raise ValueError('card_number has to consist of digits only!')
def authenticate(self, card_number=None, pin=None):
if not card_number.isdigit():
raise ValueError('card_number has to consist of digits only!')
if len(pin) > 0 and not pin.isdigit():
raise ValueError('pin has to consist of digits only or \
has to be empty!')
if len(pin) > 0 and not pin.isdigit():
raise ValueError('pin has to consist of digits only or \
has to be empty!')
try:
account = Account.objects.get(card_number=card_number)
except Account.DoesNotExist:
return None
try:
account = Account.objects.get(card_number=card_number)
except Account.DoesNotExist:
return None
if account.check_pin(pin):
return account.user
if account.check_pin(pin):
return account.user
return None
return None

11
cashonly/formfields.py

@ -3,11 +3,12 @@ from django.core.exceptions import ValidationError @@ -3,11 +3,12 @@ from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.core.validators import EMPTY_VALUES
class DigitField(CharField):
def clean(self, value):
super(DigitField, self).clean(value)
def clean(self, value):
super(DigitField, self).clean(value)
if value not in EMPTY_VALUES and not value.isdigit():
raise ValidationError(_('Please enter only digits.'))
if value not in EMPTY_VALUES and not value.isdigit():
raise ValidationError(_('Please enter only digits.'))
return value
return value

83
cashonly/management/commands/dailydigest.py

@ -13,56 +13,57 @@ import datetime @@ -13,56 +13,57 @@ import datetime
RANGE = 24
USERSETTINGS_URL = 'https://cypher/kasse/usersettings/'
class Command(NoArgsCommand):
help = 'Sends out the daily digest to all users with transactions' + \
'in the last %dh' % RANGE
help = 'Sends out the daily digest to all users with transactions' + \
'in the last %dh' % RANGE
def handle_noargs(self, **options):
translation.activate('de')
def handle_noargs(self, **options):
translation.activate('de')
tpl = get_template('cashonly/daily_digest.txt')
tpl = get_template('cashonly/daily_digest.txt')
messages = []
for a in Account.objects.all():
name = '%s %s' % (a.user.first_name, a.user.last_name)
context = {'name': name,
'credit': a.credit,
'range': RANGE,
'url': USERSETTINGS_URL}
messages = []
for a in Account.objects.all():
name = '%s %s' % (a.user.first_name, a.user.last_name)
context = {'name': name,
'credit': a.credit,
'range': RANGE,
'url': USERSETTINGS_URL}
transactions = Transaction.objects.filter(account = a) \
.filter(timestamp__gte=(datetime.datetime.now()
- datetime.timedelta(hours = RANGE)))
transactions = Transaction.objects.filter(account=a) \
.filter(timestamp__gte=(datetime.datetime.now() -
datetime.timedelta(hours=RANGE)))
if transactions.count() > 0:
lengths = {'timestamp': len(_('date')),
'description': len(_('subject')),
'amount': len(_('amount'))}
if transactions.count() > 0:
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')
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')
sum += t.amount
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
rcpts = ['%s <%s>' % (name, a.user.email)]
rcpts = ['%s <%s>' % (name, a.user.email)]
messages.append(('%s%s' % (settings.EMAIL_SUBJECT_PREFIX,
_('Account Statement')),
tpl.render(Context(context)),
settings.DEFAULT_FROM_EMAIL, rcpts))
messages.append(('%s%s' % (settings.EMAIL_SUBJECT_PREFIX,
_('Account Statement')),
tpl.render(Context(context)),
settings.DEFAULT_FROM_EMAIL, rcpts))
send_mass_mail(tuple(messages))
send_mass_mail(tuple(messages))

33
cashonly/management/commands/debtreminder.py

@ -8,25 +8,26 @@ from django.template.loader import get_template @@ -8,25 +8,26 @@ from django.template.loader import get_template
from django.utils import translation
from django.utils.translation import ugettext 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')
def handle_noargs(self, **options):
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}
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)]
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))
messages.append(('%s%s' % (settings.EMAIL_SUBJECT_PREFIX,
_('Debt Reminder')),
tpl.render(Context(context)),
settings.DEFAULT_FROM_EMAIL, rcpts))
send_mass_mail(tuple(messages))
send_mass_mail(tuple(messages))

336
cashonly/models.py

@ -11,189 +11,199 @@ from django.db import transaction @@ -11,189 +11,199 @@ from django.db import transaction
import PIL.Image
import StringIO
class Account(models.Model):
user = models.OneToOneField(User)
card_number = models.CharField(max_length=32, unique=True, blank=True,
null=True, verbose_name = _('card number'))
pin = models.CharField(max_length=32, blank=True, verbose_name = _('PIN'))
daily_digest = models.BooleanField(verbose_name = _('daily digest'), default=True)
credit = models.DecimalField(max_digits=5, decimal_places=2, default=0,
verbose_name = _('credit'))
def __unicode__(self):
return self.user.username
class Meta:
verbose_name = _('account')
verbose_name_plural = _('accounts')
@receiver(post_save, sender=User)
def user_post_save_handler(sender, instance, created, **kwargs):
if created:
# We don't have ldap_user on creation, so just add the account
account = Account(user=instance)
account.save()
else:
# When we already have an account,
# we can add the number form LDAP (mongo shit)
if hasattr(instance, 'ldap_user') \
and instance.ldap_user.attrs.has_key('employeenumber'):
instance.account.card_number = \
instance.ldap_user.attrs['employeenumber'][0]
instance.account.save()
@transaction.atomic
def change_credit(self, amount, subject, desc):
# For atomicity fetch current value first
cur = Account.objects.filter(pk=self.pk)[0]
self.credit = cur.credit + amount
self.save()
trans = Transaction(account=self, subject=subject,
amount=amount, description=desc)
trans.save()
def buy_products(self, products):
# TODO place it somewhere else
MAX_DEBIT = -35
BUY_SUBJECT = ugettext_noop('Purchase')
if min(products.values()) <= 0:
raise ValueError('Non-positive amount in products dict.')
total_value = sum(map(lambda p: p.price * products[p], products.keys()))
if self.credit - total_value >= MAX_DEBIT:
desc = ''
for product in products.keys():
if not product.active:
raise ValueError('Trying to buy a disabled product.')
amount = products[product]
logentry = SalesLogEntry(account=self, product=product,
count=amount, unit_price=product.price)
logentry.save()
desc += '%d x %s\n' % (amount, product.name)
self.change_credit(-total_value, BUY_SUBJECT, desc)
return True
else:
return False
def buy_product(self, product, amount=1):
return self.buy_products({product: amount})
def set_pin(self, pin):
# TODO: hash pin
self.pin = pin
self.save()
def clear_pin(self):
self.pin = ''
self.save()
def check_pin(self, pin):
return pin == self.pin
user = models.OneToOneField(User)
card_number = models.CharField(max_length=32, unique=True, blank=True,
null=True, verbose_name=_('card number'))
pin = models.CharField(max_length=32, blank=True, verbose_name=_('PIN'))
daily_digest = models.BooleanField(verbose_name=_('daily digest'),
default=True)
credit = models.DecimalField(max_digits=5, decimal_places=2, default=0,
verbose_name=_('credit'))
def __unicode__(self):
return self.user.username
class Meta:
verbose_name = _('account')
verbose_name_plural = _('accounts')
@receiver(post_save, sender=User)
def user_post_save_handler(sender, instance, created, **kwargs):
if created:
# We don't have ldap_user on creation, so just add the account
account = Account(user=instance)
account.save()
else:
# When we already have an account,
# we can add the number form LDAP (mongo shit)
if (hasattr(instance, 'ldap_user') and
'employeenumber' in instance.ldap_user.attrs):
instance.account.card_number = \
instance.ldap_user.attrs['employeenumber'][0]
instance.account.save()
@transaction.atomic
def change_credit(self, amount, subject, desc):
# For atomicity fetch current value first
cur = Account.objects.filter(pk=self.pk)[0]
self.credit = cur.credit + amount
self.save()
trans = Transaction(account=self, subject=subject,
amount=amount, description=desc)
trans.save()
def buy_products(self, products):
# TODO place it somewhere else
MAX_DEBIT = -35
BUY_SUBJECT = ugettext_noop('Purchase')
if min(products.values()) <= 0:
raise ValueError('Non-positive amount in products dict.')
total_value = sum(map(lambda p: p.price * products[p],
products.keys()))
if self.credit - total_value >= MAX_DEBIT:
desc = ''
for product in products.keys():
if not product.active:
raise ValueError('Trying to buy a disabled product.')
amount = products[product]
logentry = SalesLogEntry(account=self, product=product,
count=amount,
unit_price=product.price)
logentry.save()
desc += '%d x %s\n' % (amount, product.name)
self.change_credit(-total_value, BUY_SUBJECT, desc)
return True
else:
return False
def buy_product(self, product, amount=1):
return self.buy_products({product: amount})
def set_pin(self, pin):
# TODO: hash pin
self.pin = pin
self.save()
def clear_pin(self):
self.pin = ''
self.save()
def check_pin(self, pin):
return pin == self.pin
class ProductCategory(models.Model):
name = models.CharField(max_length=32, unique=True,
verbose_name = _('name'))
comment = models.CharField(max_length=128, blank=True,
verbose_name = _('comment'))
name = models.CharField(max_length=32, unique=True,
verbose_name=_('name'))
comment = models.CharField(max_length=128, blank=True,
verbose_name=_('comment'))
def __unicode__(self):
return "%s (%s)" % (self.name, self.comment)
def __unicode__(self):
return "%s (%s)" % (self.name, self.comment)
class Meta:
verbose_name = _('product category')
verbose_name_plural = _('product categories')
class Meta:
verbose_name = _('product category')
verbose_name_plural = _('product categories')
class Product(models.Model):
name = models.CharField(max_length=32, unique=True,
verbose_name = _('name'))
price = models.DecimalField(max_digits=5, decimal_places=2,
verbose_name = _('price'))
active = models.BooleanField(default = True, verbose_name = _('active'))
category = models.ForeignKey(ProductCategory, blank=True, null=True,
verbose_name = _('category'))
image = models.ImageField(upload_to="products", verbose_name = _('image'),
blank=True, null=True)
image_thumbnail = models.ImageField(upload_to="products_thumb",
verbose_name = _('image'),
blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
verbose_name = _('product')
verbose_name_plural = _('products')
name = models.CharField(max_length=32, unique=True,
verbose_name=_('name'))
price = models.DecimalField(max_digits=5, decimal_places=2,
verbose_name=_('price'))
active = models.BooleanField(default=True, verbose_name=_('active'))
category = models.ForeignKey(ProductCategory, blank=True, null=True,
verbose_name=_('category'))
image = models.ImageField(upload_to="products", verbose_name=_('image'),
blank=True, null=True)
image_thumbnail = models.ImageField(upload_to="products_thumb",
verbose_name=_('image'),
blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
verbose_name = _('product')
verbose_name_plural = _('products')
@receiver(pre_save, sender=Product)
def product_post_save_handler(sender, instance, **kwargs):
img = instance.image
if img:
scaledFile = StringIO.StringIO()
img.open(mode='r')
with img:
scaled = PIL.Image.open(img)
thumbnail_size = getattr(settings, 'THUMBNAIL_SIZE', (150, 150))
scaled.thumbnail(thumbnail_size, PIL.Image.ANTIALIAS)
scaled.save(scaledFile, 'PNG')
scaledFile.seek(0)
img = instance.image
if img:
scaledFile = StringIO.StringIO()
img.open(mode='r')
with img:
scaled = PIL.Image.open(img)
thumbnail_size = getattr(settings, 'THUMBNAIL_SIZE', (150, 150))
scaled.thumbnail(thumbnail_size, PIL.Image.ANTIALIAS)
scaled.save(scaledFile, 'PNG')
scaledFile.seek(0)
instance.image_thumbnail.save(img.url, File(scaledFile), save=False)
instance.image_thumbnail.save(img.url, File(scaledFile), save=False)
class ProductBarcode(models.Model):
barcode = models.CharField(max_length=32, unique=True,
verbose_name = _('barcode'))
comment = models.CharField(max_length=128, blank=True,
verbose_name = _('comment'))
product = models.ForeignKey(Product, verbose_name = _('product'))
barcode = models.CharField(max_length=32, unique=True,
verbose_name=_('barcode'))
comment = models.CharField(max_length=128, blank=True,
verbose_name=_('comment'))
product = models.ForeignKey(Product, verbose_name=_('product'))
def __unicode__(self):
return self.barcode
def __unicode__(self):
return self.barcode
class Meta:
verbose_name = _('barcode')
verbose_name_plural = _('barcodes')
class Meta:
verbose_name = _('barcode')
verbose_name_plural = _('barcodes')
class Transaction(models.Model):
account = models.ForeignKey(Account, verbose_name = _('account'))
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(max_digits=5, decimal_places=2,
verbose_name = _('amount'))
class Meta:
verbose_name = _('transaction')
verbose_name_plural = _('transactions')
account = models.ForeignKey(Account, verbose_name=_('account'))
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(max_digits=5, decimal_places=2,
verbose_name=_('amount'))
class Meta:
verbose_name = _('transaction')
verbose_name_plural = _('transactions')
class SalesLogEntry(models.Model):
account = models.ForeignKey(Account, verbose_name = _('account'))
product = models.ForeignKey(Product, verbose_name = _('product'))
count = models.IntegerField(verbose_name = _('count'))
unit_price = models.DecimalField(max_digits=5, decimal_places=2,
verbose_name = _('unit price'))
timestamp = models.DateTimeField(auto_now_add=True,
verbose_name = _('timestamp'))
def __unicode__(self):
return '%dx %s - %s' % (self.count, self.product, self.account)
class Meta:
verbose_name = _('sales log entry')
verbose_name_plural = _('sales log entries')
account = models.ForeignKey(Account, verbose_name=_('account'))
product = models.ForeignKey(Product, verbose_name=_('product'))
count = models.IntegerField(verbose_name=_('count'))
unit_price = models.DecimalField(max_digits=5, decimal_places=2,
verbose_name=_('unit price'))
timestamp = models.DateTimeField(auto_now_add=True,
verbose_name=_('timestamp'))
def __unicode__(self):
return '%dx %s - %s' % (self.count, self.product, self.account)
class Meta:
verbose_name = _('sales log entry')
verbose_name_plural = _('sales log entries')
@receiver(pre_delete, sender=SalesLogEntry)
def logentry_pre_delete_handler(sender, instance, **kwargs):
SUBJECT = ugettext_noop('Cancellation')
DESC = '%d x %s'
instance.account.change_credit(
instance.unit_price * instance.count,
SUBJECT, DESC % (instance.count, instance.product.name)
)
SUBJECT = ugettext_noop('Cancellation')
DESC = '%d x %s'
instance.account.change_credit(
instance.unit_price * instance.count,
SUBJECT, DESC % (instance.count, instance.product.name)
)

33
cashonly/urls.py

@ -2,29 +2,32 @@ from django.conf.urls import url @@ -2,29 +2,32 @@ from django.conf.urls import url
from cashonly import views
urlpatterns = [
url(r'^$', views.overview, name='overview'),
url(r'^$', views.overview, name='overview'),
url(r'^product/(?P<pk>\d+)/$', views.ProductView.as_view(), name='product'),
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/$', 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+)/$', views.transactions,
{'detailed': False}, name='transactions'),
url(r'transactions/(?P<page>\d+)/detailed/$', views.transactions, {'detailed': True},
name='transactions_detailed'),
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'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+)/$', views.buy, name='buy'),
url(r'buy/(?P<product_id>\d+)/really/$', views.buy,
{'confirm': True}, name='buy_really'),
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/thanks/$', views.buy_thanks, name='buy_thanks'),
url(r'buy/error/$', views.buy_error, name='buy_error'),
url(r'buy/error/$', views.buy_error, name='buy_error'),
url(r'usersettings(/(?P<submit>\w+))?/$', views.usersettings,
name='usersettings'),
url(r'usersettings(/(?P<submit>\w+))?/$', views.usersettings,
name='usersettings'),
]

4
cashonly/version.py

@ -1,3 +1 @@ @@ -1,3 +1 @@
CASHONLY_VERSION='2.1.2'
CASHONLY_VERSION = '2.1.2'

49
cashonly/views.py

@ -9,22 +9,27 @@ from django.utils.translation import ugettext_lazy @@ -9,22 +9,27 @@ from django.utils.translation import ugettext_lazy
import cashonly.version
import datetime
def version_number_context_processor(request):
return {'version_number': cashonly.version.CASHONLY_VERSION}
@login_required
def overview(request):
a = request.user.account
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]
# 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]
products = []
# Find 3 products
for p in purchases:
if not p in products:
if p not in products:
products.append(p)
if len(products) == 3:
@ -53,8 +58,10 @@ def transactions(request, detailed, page): @@ -53,8 +58,10 @@ def transactions(request, detailed, page):
except paginator.EmptyPage:
transaction_list = paginator.page(paginator.num_pages)
return render(request, 'cashonly/transaction_list.html', { 'transaction_list': transaction_list,
'detailed': detailed })
return render(request, 'cashonly/transaction_list.html',
{'transaction_list': transaction_list,
'detailed': detailed})
def products(request, category_id=None):
if category_id is None:
@ -62,13 +69,15 @@ def products(request, category_id=None): @@ -62,13 +69,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/product_list.html', { 'product_list': products,
'category': category,
'categories': categories })
return render(request, 'cashonly/product_list.html',
{'product_list': products, 'category': category,
'categories': categories})
@login_required
def buy(request, product_id, confirm=False):
@ -80,20 +89,25 @@ def buy(request, product_id, confirm=False): @@ -80,20 +89,25 @@ def buy(request, product_id, confirm=False):
else:
return redirect('buy_error')
else:
return render(request, 'cashonly/buy_confirm.html', {'product': product})
return render(request, 'cashonly/buy_confirm.html',
{'product': product})
@login_required
def buy_thanks(request):
return render(request, 'cashonly/buy_thanks.html')
@login_required
def buy_error(request):
return render(request, 'cashonly/buy_error.html')
class UserSettingsForm(forms.Form):
daily_digest = forms.BooleanField(required=False,
label=ugettext_lazy('daily digest'))
class UserPinForm(forms.Form):
pin = forms.CharField(max_length=32, widget=forms.PasswordInput,
label=ugettext_lazy('PIN'), required=False)
@ -104,14 +118,14 @@ class UserPinForm(forms.Form): @@ -104,14 +118,14 @@ class UserPinForm(forms.Form):
def clean(self):
cleaned_data = super(UserPinForm, self).clean()
if not (cleaned_data.has_key('pin') or
cleaned_data.has_key('pin_confirm')):
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.'))
return cleaned_data
@login_required
def usersettings(request, submit=None):
daily_digest = request.user.account.daily_digest
@ -137,10 +151,5 @@ def usersettings(request, submit=None): @@ -137,10 +151,5 @@ def usersettings(request, submit=None):
request.user.account.save()
return render(request, 'cashonly/usersettings_saved.html')
return render(request, 'cashonly/usersettings.html', { 'settings_form': settings_form,
'pin_form': pin_form})
return render(request, 'cashonly/usersettings.html',
{'settings_form': settings_form, 'pin_form': pin_form})

Loading…
Cancel
Save