diff --git a/cashonly/admin.py b/cashonly/admin.py
index 1520f09..58641e7 100644
--- a/cashonly/admin.py
+++ b/cashonly/admin.py
@@ -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 '%s' % \
- (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 '%s' % \
+ (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)
-
diff --git a/cashonly/auth.py b/cashonly/auth.py
index 268eecf..a0263ad 100644
--- a/cashonly/auth.py
+++ b/cashonly/auth.py
@@ -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
diff --git a/cashonly/formfields.py b/cashonly/formfields.py
index 98c4216..c9049a5 100644
--- a/cashonly/formfields.py
+++ b/cashonly/formfields.py
@@ -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
diff --git a/cashonly/management/commands/dailydigest.py b/cashonly/management/commands/dailydigest.py
index 694a9d1..4dcea6f 100644
--- a/cashonly/management/commands/dailydigest.py
+++ b/cashonly/management/commands/dailydigest.py
@@ -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))
diff --git a/cashonly/management/commands/debtreminder.py b/cashonly/management/commands/debtreminder.py
index 09a10f9..950a25b 100644
--- a/cashonly/management/commands/debtreminder.py
+++ b/cashonly/management/commands/debtreminder.py
@@ -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))
diff --git a/cashonly/models.py b/cashonly/models.py
index 30ea3e9..9075539 100644
--- a/cashonly/models.py
+++ b/cashonly/models.py
@@ -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)
+ )
diff --git a/cashonly/urls.py b/cashonly/urls.py
index 5e6f427..e8b7094 100644
--- a/cashonly/urls.py
+++ b/cashonly/urls.py
@@ -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\d+)/$', views.ProductView.as_view(), name='product'),
+ 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/$', views.transactions, {'detailed': False, 'page': 1},
+ name='transactions'),
- url(r'transactions/(?P\d+)/$', views.transactions, {'detailed': False}, 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'transactions/(?P\d+)/detailed/$', views.transactions,
+ {'detailed': True}, name='transactions_detailed'),
- url(r'products/((?P\d+)/)?$', views.products, name='products'),
+ url(r'products/((?P\d+)/)?$', views.products,
+ name='products'),
- url(r'buy/(?P\d+)/$', views.buy, name='buy'),
+ 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/(?P\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\w+))?/$', views.usersettings,
- name='usersettings'),
+ url(r'usersettings(/(?P\w+))?/$', views.usersettings,
+ name='usersettings'),
]
-
diff --git a/cashonly/version.py b/cashonly/version.py
index 412bb46..97b7d39 100644
--- a/cashonly/version.py
+++ b/cashonly/version.py
@@ -1,3 +1 @@
-
-CASHONLY_VERSION='2.1.2'
-
+CASHONLY_VERSION = '2.1.2'
diff --git a/cashonly/views.py b/cashonly/views.py
index 4ac3b86..62710d0 100644
--- a/cashonly/views.py
+++ b/cashonly/views.py
@@ -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):
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):
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):
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):
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):
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})