diff --git a/k4ever/api/views.py b/k4ever/api/views.py index 8ada0eb..5ff32e9 100644 --- a/k4ever/api/views.py +++ b/k4ever/api/views.py @@ -42,19 +42,15 @@ def buyItem(request, itemid, buymode=""): "with/deposit" item and deposit "only/deposit" only deposit """ - order = Order() - order.create(user) + order = Order(user=request.user) order.save() # for the id! if buymode == "" or buymode == "with/deposit": - p = Purchase.create(order, item, isDeposit=False) - p.order = order - p.save() + p = Purchase(order=order, buyable=item, isDeposit=False) + p.save(saveOrder=False) # TANNEK! if buymode == "with/dopsit" or buymode == "only/deposit": if buymode == "with/deposit" or buymode == "only/deposit": - p = Purchase.create(order, item, isDeposit=True) - p.order = order - p.save() - order.updatePrice(commit=True) + p = Purchase(order=order, buyable=item, isDeposit=True) + p.save(saveOrder=False) order.save() #return json-container diff --git a/k4ever/api2/handlers.py b/k4ever/api2/handlers.py index d0b3996..60a31fb 100644 --- a/k4ever/api2/handlers.py +++ b/k4ever/api2/handlers.py @@ -92,18 +92,16 @@ class BuyableItemHandler(BaseHandler): if (not item.hasDeposit() and deposit != self.BUY_ITEM) or \ deposit not in (self.BUY_ITEM, self.BUY_DEPOSIT, self.BUY_ITEM_AND_DEPOSIT): return rc.BAD_REQUEST - order = Order() - order.create(request.user) + order = Order(user=request.user) order.save() for i in range(amount): if deposit == self.BUY_ITEM or deposit == self.BUY_ITEM_AND_DEPOSIT: - p = Purchase.create(order, item, isDeposit=False) - p.save() + p = Purchase(order=order, buyable=item, isDeposit=False) + p.save(saveOrder=False) if deposit == self.BUY_DEPOSIT or deposit == self.BUY_ITEM_AND_DEPOSIT: - p = Purchase.create(order, item, isDeposit=True) - p.save() - order.updatePrice(commit=True) + p = Purchase(order=order, buyable=item, isDeposit=True) + p.save(saveOrder=False) order.save() return rc.CREATED @@ -159,20 +157,16 @@ class BuyableItemHandler(BaseHandler): if item in request.data['deposits'] and not ids[item].hasDeposit(): return getError(rc.BAD_REQUEST, "Item '%s' cant be bought with deposit" % (item,)) - order = Order() - order.create(request.user) - order.save() - purchases = [] + order = Order(user=request.user) + order.save() # one save, for the id # buy items for item in request.data['items']: - p = Purchase.create(order, ids[item], isDeposit=False) - p.save() + p = Purchase(order=order, buyable=ids[item], isDeposit=False) + p.save(saveOrder=False) # buy deposits for item in request.data['deposits']: - p = Purchase.create(order, ids[item], isDeposit=True) - p.save() - - order.updatePrice(commit=True) + p = Purchase(order=order, buyable=ids[item], isDeposit=True) + p.save(saveOrder=False) order.save() return rc.CREATED diff --git a/k4ever/buyable/models.py b/k4ever/buyable/models.py index 5ae0890..5ce271b 100644 --- a/k4ever/buyable/models.py +++ b/k4ever/buyable/models.py @@ -3,6 +3,7 @@ from django.db import models from django.contrib.auth.models import User import datetime from decimal import Decimal +from django.db.models.signals import pre_delete, post_delete # Create your models here. class BuyableType(models.Model): @@ -31,18 +32,6 @@ class Buyable(models.Model): """ Returns True if the item has deposit. """ return self.deposit > Decimal(0) - def createPurchase(self, isDeposit=False): - """ Creates a :class:`Purchase` containing this :class:`Buyable`. """ - p = Purchase() - if isDeposit: - p.price = self.deposit - else: - p.price = self.price - p.dateTime = datetime.datetime.now() - p.buyable = self - - return p - def __unicode__(self): item = "%s (%.2f EUR" % (self.name, self.price) if self.hasDeposit(): @@ -59,30 +48,19 @@ class Buyable(models.Model): class Order(models.Model): """ Represents an order by the user, consists of several :class:`Purchases `. """ user = models.ForeignKey(User) - price = models.DecimalField(max_digits=8, decimal_places=2) - dateTime = models.DateTimeField() - - def create(self, user=None): - models.Model.__init__(self) - self.price = Decimal(0) - self.dateTime = datetime.datetime.now() - if user: - self.user = user + price = models.DecimalField(max_digits=8, decimal_places=2, default=Decimal(0)) + dateTime = models.DateTimeField(auto_now_add=True) def addItems(self, items): for item in items: self.purchase.add(item) self.price += item.price - def updatePrice(self, commit=False, updateBalance=False): - self.price = Decimal(0) - for item in self.purchase_set.all(): - self.price += item.price - if commit or updateBalance: - # TROLL MODE ON! - profile = self.user.get_profile() - profile.balance -= self.price - profile.save() + def updatePrice(self, instance, price, saveOrder=True): + """ Update price of order, save Order if saveOrder is True. """ + self.price += price + if self.id and saveOrder: + self.save() def __unicode__(self): return "Price %s, User %s" % (self.price, self.user) @@ -92,40 +70,86 @@ class Order(models.Model): for item in self.purchase_set.all(): l += item.buyable.name + u", " return l.rstrip(u", ") + + def save(self, *args, **kwargs): + profile = self.user.get_profile() + if self.id == None: + # new item, get it! + profile.balance -= self.price + profile.save() + else: + # get old + oldobj = Order.objects.get(id=self.id) + if oldobj.user == self.user: + profile = self.user.get_profile() + profile.balance -= (self.price - oldobj.price) + profile.save() + else: + oldProfile = oldobj.user.get_profile() + newProfile = self.user.get_profile() + oldProfile.balance += oldobj.price + oldProfile.save() + newprofile.balance -= self.price + self.save() + super(Order, self).save(*args, **kwargs) + + @staticmethod + def pre_delete_signal(sender, instance, **kwargs): + # Normally we would not need this function as for consistency + # reasons and the behaviour of Buyables delete at deletion + # the orders price should be 0. But for the sake of not getting + # inconsistent with the users balance in the strangest situations + # we will check that here. This means (at least) one extra query + # per order deletion, as we don't get the updated object but the one + # where all the buyables have not updated the price + updOrder = Order.objects.get(id=instance.id) + if updOrder.price != Decimal("0"): + profile = updOrder.user.get_profile() + profile.balance += updOrder.price + profile.save() - #def save(self, *args, **kwargs): - #profile = self.user.get_profile() - #if self.id == None: - ## new item, get it! - #profile.balance -= self.price - #profile.save() - #else: - ## get old - #super(Order, self).save(*args, **kwargs) - +pre_delete.connect(Order.pre_delete_signal, sender=Order) class Purchase(models.Model): - """ Represents a :class:`Buyable` in a :class:`Order`""" + """ Represents a :class:`Buyable` in a :class:`Order`. + + Could be viewed as 'one bought item' or 'one item on the voucher'.""" order = models.ForeignKey(Order) - price = models.DecimalField(max_digits=8, decimal_places=2) + price = models.DecimalField(max_digits=8, decimal_places=2, default=None) isDeposit = models.BooleanField() buyable = models.ForeignKey(Buyable) - - @staticmethod - def create(order, buyable, isDeposit=False): - """ Create a Purchase from a buyable. Still needs to be saved. """ - p = Purchase() - p.order = order - p.isDeposit = isDeposit - if p.isDeposit: - p.price = buyable.deposit + + def updatePrice(self): + if self.isDeposit: + self.price = self.buyable.deposit else: - p.price = buyable.price - p.dateTime = datetime.datetime.now() - p.buyable = buyable - - return p - + self.price = self.buyable.price + def __unicode__(self): return "%s%s" % (self.buyable.name, (self.isDeposit and " (deposit)" or "")) + def save(self, saveOrder=True, *args, **kwargs): + if self.price == None: + self.updatePrice() + + if self.order: + if self.id == None: + self.order.updatePrice(self, self.price, saveOrder=saveOrder) + else: + # object exists, update. + oldobj = Purchase.objects.get(id=self.id) + if oldobj.order == self.order: + self.order.updatePrice(self, self.price - oldobj.price, saveOrder=saveOrder) + else: + oldobj.order.updatePrice(oldobj, -oldobj.price, saveOrder=saveOrder) + self.order.updatePrice(self, self.price, saveOrder=saveOrder) + super(Purchase, self).save(*args, **kwargs) + + @staticmethod + def pre_delete_signal(sender, instance, **kwargs): + # FIXME: For many items this could get very inefficient + # Find workarround! + print "pre delete from purchase" + instance.order.updatePrice(instance, -instance.price, saveOrder=True) + +pre_delete.connect(Purchase.pre_delete_signal, sender=Purchase) diff --git a/k4ever/buyable/views.py b/k4ever/buyable/views.py index d956e19..add2088 100644 --- a/k4ever/buyable/views.py +++ b/k4ever/buyable/views.py @@ -47,19 +47,14 @@ def buyItem(request, itemid, buymode=""): "with/deposit" item and deposit "only/deposit" only deposit """ - order = Order() - order.create(user) + order = Order(user=request.user) order.save() # for the id! if buymode == "" or buymode == "with/deposit": - p = Purchase.create(order, item, isDeposit=False) - p.order = order - p.save() - # TANNEK! if buymode == "with/dopsit" or buymode == "only/deposit": + p = Purchase(order=order, buyable=item, isDeposit=False) + p.save(saveOrder=False) if buymode == "with/deposit" or buymode == "only/deposit": - p = Purchase.create(order, item, isDeposit=True) - p.order = order - p.save() - order.updatePrice(commit=True) + p = Purchase(order=order, buyable=item, isDeposit=True) + p.save(saveOrder=False) order.save() return HttpResponseRedirect("/store/bought/%s/" % (order.id)) diff --git a/k4ever/transaction/models.py b/k4ever/transaction/models.py index e889807..d0c86de 100644 --- a/k4ever/transaction/models.py +++ b/k4ever/transaction/models.py @@ -25,12 +25,10 @@ class Transaction(models.Model): dateTime = models.DateTimeField(auto_now_add=True) amount = models.DecimalField(max_digits=8, decimal_places=2, validators=[validate_notZero]) checked = models.BooleanField(default=False) - def __unicode__(self): return u"%s for user %s (%s),%schecked" % (self.amount, self.user, self.transactionType, (self.checked and " " or " not ")) - # TODO: Find out what would happen if parent save/delete does not like us def save(self, *args, **kwargs): profile = self.user.get_profile() if self.id == None: