from piston.handler import BaseHandler from piston.utils import rc from k4ever.buyable.models import * from k4ever.transaction.models import * from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.models import Group from django.core.exceptions import MultipleObjectsReturned from decorators import * from collections import Iterable from decimal import Decimal, InvalidOperation from helper import * from django.db.models import Q import datetime class BuyableItemHandler(BaseHandler): """Handler responsible for getting and buying items.""" allowed_methods = ('GET', 'POST') #fields = ('id', 'description') model = Buyable exclude = ('_*',) BUY_ITEM, BUY_DEPOSIT, BUY_ITEM_AND_DEPOSIT = range(3) def read(self, request, itemId=None, bulkBuy=False): """Get one or multiple items. - type: Only get items belonging to this type - barcode: Return only item(s) with this barcode Note that neither type nor barcode is used if an item id is specified. """ if bulkBuy: ret = rc.NOT_IMPLEMENTED ret.write("\nBulk buying does not support GET\n") return ret barcode = request.GET.get('barcode', None) if itemId == None: obj = Buyable.objects.all() if barcode and barcode != '': # try to get specific object or return 404 try: return Buyable.objects.get(barcode=barcode) except MultipleObjectsReturned: ret = rc.DUPLICATE_ENTRY ret.write("\nWe found more than one entry with this barcode. Bad.\n") return ret except Buyable.DoesNotExist: return rc.NOT_FOUND else: if request.GET.has_key('type'): obj = Buyable.objects.filter(buyableType__name=request.GET['type']) return obj try: return Buyable.objects.get(id=itemId) except Buyable.DoesNotExist: error = rc.NOT_FOUND error.write("This buyable does not exist in our database") return error @manglePluginPerms def create(self, request, itemId=None, bulkBuy=False): """Buy one or multiple :class:`Buyable ` items. """ if not request.content_type: request.data = request.POST if bulkBuy: return self.bulkBuy(request) else: return self.buyItem(request, itemId) def buyItem(self, request, itemId): """Buy a :class:`Buyable ` item. - deposit: Set to 0 for no deposit, 1 for deposit and 2 for item+deposit (default 0) - amount: amount of items to buy (default 1) """ if not itemId: return rc.BAD_REQUEST item = None try: item = Buyable.objects.get(id=itemId) except Buyable.DoesNotExist: return rc.NOT_FOUND # parse post data deposit = getInt(request.data, 'deposit', self.BUY_ITEM) amount = getInt(request.data, 'amount', 1) if amount < 1: return rc.BAD_REQUEST if amount > 30: ret = rc.BAD_REQUEST ret.write("\nYou are trying to buy more than 30 items at once. This is not permitted. If you think it should, mail the admins / fix this in the handlers.py\n"); return ret 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.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() 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) order.save() return rc.CREATED def bulkBuy(self, request): """Buy a :class:`Buyable ` item. To buy multiple items, the body of the POST-request has to be either JSON or YAML. - items: List of items to buy. - deposits: List of items to buy deposit for. """ if not request.content_type: ret = rc.BAD_REQUEST ret.write("\nThe content-type of the request must not be empty/urlencoded\n") return ret if not request.data.has_key("items") and not request.data.has_key("deposits"): ret = rc.BAD_REQUEST ret.write("\nYou need to specify either items or deposits (or both).\n") return ret if not request.data.has_key("items"): request.data['items'] = [] if not request.data.has_key("deposits"): request.data['deposits'] = [] itemList = [] try: if not isinstance(request.data['items'], Iterable): raise TypeError() itemList += request.data['items'] if request.data.has_key('items'): if not isinstance(request.data['deposits'], Iterable): raise TypeError() itemList += request.data['deposits'] except TypeError: ret = rc.BAD_REQUEST ret.write("\nThe items/deposists parameter have to be a list.\n") return ret if len(itemList) > 30: ret = rc.BAD_REQUEST ret.write("\nYou are trying to buy more than 30 items at once. This is not permitted. If you think it should, mail the admins / fix this in the handlers.py\n"); return ret if len(itemList) == 0: ret = rc.BAD_REQUEST ret.write("\nYour request contains no items/deposits.\n") return ret ids = {} for item in itemList: if not ids.has_key(item): try: ids[item] = Buyable.objects.get(id=item) except Buyable.DoesNotExist: ret = rc.NOT_FOUND ret.write("\nThe item with the id '%s' could not be found\n" % (item,)) return ret except ValueError: ret = rc.NOT_FOUND ret.write("\nItem ids should be numeric (and preferably integers)\n") return ret if item in request.data['deposits'] and not ids[item].hasDeposit(): ret = rc.BAD_REQUEST ret.write("\nItem '%s' cant be bought with deposit\n" % (item,)) return ret order = Order() order.create(request.user) order.save() purchases = [] # buy items for item in request.data['items']: p = Purchase.create(order, ids[item], isDeposit=False) p.save() # buy deposits for item in request.data['deposits']: p = Purchase.create(order, ids[item], isDeposit=True) p.save() order.updatePrice(commit=True) order.save() return rc.CREATED class BuyableTypeHandler(BaseHandler): """Handler for listing all :class:`BuyableType `. This class only supports read requests which won't accept any arguments. It will give you a list of buyable-types. Nothing more, nothing less. """ allowed_methods = ('GET',) model = BuyableType class HistoryHandler(BaseHandler): """Handler providing access to the user's history """ allowed_methods = ('GET',) fields = ('id', 'price', 'dateTime', ('purchase_set', (('buyable', ('id', )), 'price', 'name'))) @manglePluginPerms def read(self, request): """Get the user's history - num: Number of entries to return """ num = getInt(request.GET, 'num', 0) qset = Order.objects.filter(user=request.user).order_by("-dateTime") if num > 0: return qset[:num] return qset class TransactionTransactHandler(BaseHandler): """Handler for transaction. This handler takes care of adding money to accounts and returning previous money transfers """ allowed_methods = ('GET', 'POST') model = Transaction fields = ('amount', 'dateTime', 'checked', ('transactionType', ('id', 'name'))) @manglePluginPerms def read(self, request): """Return the user's last transactions - num: Number of entries to return """ num = getInt(request.GET, 'num', 0) if num < 0: return rc.BAD_REQUEST userTrans = Transaction.objects.filter(user=request.user).order_by("-dateTime") if num > 0: return userTrans[:num] return userTrans @manglePluginPerms def create(self, request): """Transact money to an account - amount: [req] Amount to add to the user's account - type: [req] Type of transaction (id) """ amount = getDecimal(request.POST, 'amount', Decimal(0)) tTypeId = getInt(request.POST, 'type', -1) if amount < Decimal("0.01"): ret = rc.BAD_REQUEST ret.write("\nA negative amount (or zeroed) is not supported right now (there has not been put enough thought into the 'lending money' process\n") return ret tType = None try: tType = TransactionType.objects.get(id=tTypeId) except TransactionType.DoesNotExist: ret = rc.BAD_REQUEST ret.write("\nYour TransactionType could not be found\n") return ret trans = Transaction() trans.user = request.user trans.transactionType = tType trans.dateTime = datetime.datetime.now() trans.amount = amount trans.save() return rc.ALL_OK class TransactionTypeHandler(BaseHandler): """Handler for :class:`Transaction Types ` Supplies a list of Transaction Types """ allowed_methods = ('GET',) model = TransactionType class TransactionVirtualHandler(BaseHandler): """ Handler for :class:`Virtual Transaction ` Allows to make transactions between users. """ allowed_methods = ('GET', 'POST') model = VirtualTransaction fields = ('id', 'amount', 'dateTime', 'comment', ('user', ('id', 'username',)), ('recipient', ('id', 'username',))) @manglePluginPerms def read(self, request): """Return the user's last virtual transactions (inbound and outbound) - num: Number of entries to return """ num = getInt(request.GET, 'num', 0) if num < 0: return rc.BAD_REQUEST userTrans = VirtualTransaction.objects.filter(Q(user=request.user) | Q(recipient=request.user)).order_by("-dateTime") if num > 0: return userTrans[:num] return userTrans @manglePluginPerms def create(self, request): """ Transact money from the users to another users account. - amount: [req] Amount to transact - recipient: [req] User that will get the money - comment: [req] Comment, why the money was transacted """ amount = getDecimal(request.data, 'amount', Decimal(0)) if amount < Decimal("0.01"): ret = rc.BAD_REQUEST ret.write("\nYou can't transact negatives amount to another users account.\n") return ret comment = request.data.get('comment', None) if not comment: ret = rc.BAD_REQUEST ret.write("\nPlease supply a comment for the transaction\n") return ret recipientStr = request.data.get('recipient', None) recipient = None try: recipient = User.objects.get(username=recipientStr) except User.DoesNotExist: ret = rc.BAD_REQUEST ret.write("\nThe recipient user does not exist.\n") return ret trans = VirtualTransaction(user=request.user, recipient=recipient, amount=amount, comment=comment) trans.save() return rc.ALL_OK class AccountBalanceHandler(BaseHandler): """Handler for the user's account balance""" allowed_methods = ('GET',) @manglePluginPerms def read(self, request): """Returns the user's current account balance""" balance = request.user.get_profile().balance return {'balance': balance} class AuthBlobHandler(BaseHandler): """Handler to read and write a user's authblob Currently these functions are only available for a plugin user. Other users will get a rc.FORBIDDEN. Keep in mind that to use these functions, a plugin needs the permissions to do this in its configuration. """ allowed_methods = ('GET', 'POST') @requirePlugin @manglePluginPerms def read(self, request): """Read the user's authblob To use this function the plugin needs :attr:`main.models.Plugin.pluginCanReadAuthblob` to be true. """ if not request.plugin.pluginCanReadAuthblob: ret = rc.FORBIDDEN ret.write("\nThis plugin is not allowed to read the user's authblob\n") return ret return {'authblob': request.pluginperms.authblob} @requirePlugin @manglePluginPerms def create(self, request): """Write the user's authblob. To use this function the plugin needs :attr:`main.models.Plugin.pluginCanWriteAuthblob` to be true. .. Note:: In fact this method *should* be an update method, but for compability reasons (wget usage) it was decided to make this accessible as a create (POST) hook. """ if not request.plugin.pluginCanWriteAuthblob: ret = rc.FORBIDDEN ret.write("\nThis plugin is not allowed to write the user's authblob\n") return ret if not request.data.has_key('authblob'): ret = rc.BAD_REQUEST ret.write("\nTo change the user's auth blob you actually need to provide one\n") request.pluginperms.authblob = request.data['authblob'] request.pluginperms.authblob.save() return rc.ALL_OK class AuthUserHandler(BaseHandler): """ Handler for mapping an authblob to a user This handler is only available to plugins and only if :attr:`unique authblob ` is set for this plugin. Then it will provide a mapping from an authblob to a specific user. """ allowed_methods = ('GET') fields = ('id', 'username') @requirePlugin def read(self, request): """Returns an user if one can be found, else rc.NOT_FOUND - authblob: [required] Authblob to search """ if not request.plugin.uniqueAuthblob: ret = rc.BAD_REQUEST ret.write("\nThis plugin does not support unique auth blobs, therefore we can't identify a user uniquely by their authblob\n") return ret if not request.GET.has_key('authblob'): return rc.BAD_REQUEST try: perm = PluginPermission.objects.get(plugin=request.plugin, authblob=request.GET['authblob']) return perm.user except PluginPermission.DoesNotExist: return rc.NOT_FOUND class ConfigHandler(BaseHandler): """ Handler for API configuration values This handler provides some API related configuration values""" allowed_methods = ('GET',) def read(self, request): """Get API configuration values Currently returns the API version and mediaurl (where to find images etc.) """ return { 'version': '0.1', 'mediaurl': 'http://devcat.someserver.de:13805/media', }