# This file is part of k4ever, a point-of-sale system # Contact............ # Website............ http://k4ever.someserver.de/ # Bug tracker........ http://k4ever.someserver.de/report # # Licensed under GNU Affero General Public License v3 or later from easy_thumbnails.files import get_thumbnailer 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 main.helper import getUserFromAuthblob from collections import Iterable from decimal import Decimal, InvalidOperation from helper import * from django.db.models import Q import datetime # Available image sizes for thumbnails THUMB_SIZES = [(48,48), (64,64), (100,100), (150,150), (220, 220), (330, 330), (470, 470), (680, 680)] 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: return getError(rc.NOT_IMPLEMENTED, "Bulk buying does not support GET") 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: return getError(rc.DUPLICATE_ENTRY, "We found more than one entry with this barcode. Bad.") 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: return getError(rc.NOT_FOUND, "This buyable does not exist in our database") @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: return getError(rc.BAD_REQUEST, "You 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") 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(user=request.user) order.save() for i in range(amount): if deposit == self.BUY_ITEM or deposit == self.BUY_ITEM_AND_DEPOSIT: 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(order=order, buyable=item, isDeposit=True) p.save(saveOrder=False) 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: return getError(rc.BAD_REQUEST, "The content-type of the request must not be empty/urlencoded") if not request.data.has_key("items") and not request.data.has_key("deposits"): return getError(rc.BAD_REQUEST, "You need to specify either items or deposits (or both).") 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: return getError(rc.BAD_REQUEST, "The items/deposists parameter have to be a list.") if len(itemList) > 30: return getError(rc.BAD_REQUEST, "You 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") if len(itemList) == 0: return getError(rc.BAD_REQUEST, "Your request contains no items/deposits.") 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: return getError(rc.NOT_FOUND, "Item ids should be numeric (and preferably integers)") 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(user=request.user) order.save() # one save, for the id # buy items for item in request.data['items']: p = Purchase(order=order, buyable=ids[item], isDeposit=False) p.save(saveOrder=False) # buy deposits for item in request.data['deposits']: p = Purchase(order=order, buyable=ids[item], isDeposit=True) p.save(saveOrder=False) 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', 'name')), 'price', 'isDeposit'))) @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 ImgSizesHandler(BaseHandler): """ Handler for listing all available tumbnailsizes """ allowed_methods = ('GET',) def read(self, request): return THUMB_SIZES class ImgThumbHandler(BaseHandler): """ Handler providing access to thumbnails for images of buyables """ allowed_methods = ('GET',) def read(self, request, itemId, xSize, ySize): thumbSize = None try: thumbSize = (int(xSize), int(ySize)) except ValueError: ret = rc.BAD_REQUEST ret.write("\nSomething is seriously broken, django urls SHOULD have parsed out the non-number thingies\n") return ret if thumbSize not in THUMB_SIZES: ret = rc.BAD_REQUEST ret.write("\nThe requested thumbnailsize is not available\n") return ret item = None try: item = Buyable.objects.get(id=itemId) except Buyable.DoesNotExist: ret = rc.NOT_FOUND ret.write("The item with the id '%s' could not be found\n" % (itemId,)) return ret thumbnail_options = dict(size=thumbSize) thumb = get_thumbnailer(item.image).get_thumbnail(thumbnail_options) return thumb._get_url() 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 (!= 0) - type: [req] Type of transaction (id) """ if not request.content_type: request.data = request.POST amount = getDecimal(request.data, 'amount', Decimal(0)) tTypeId = getInt(request.data, 'type', -1) if amount == Decimal("0.0"): return getError(rc.BAD_REQUEST, "An amount equaling zero is not supported") tType = None try: tType = TransactionType.objects.get(id=tTypeId) except TransactionType.DoesNotExist: return getError(rc.BAD_REQUEST, "Your TransactionType could not be found") trans = Transaction(user=request.user, transactionType=tType, amount=amount, checked=not tType.needsCheck) 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"): return getError(rc.BAD_REQUEST, "You can't transact negatives amount to another users account.") comment = request.data.get('comment', None) if not comment: return getError(rc.BAD_REQUEST, "Please supply a comment for the transaction") recipientStr = request.data.get('recipient', None) recipient = None try: recipient = User.objects.get(username=recipientStr) except User.DoesNotExist: return getError(rc.BAD_REQUEST, "The recipient user does not exist.") 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: return getError(rc.FORBIDDEN, "This plugin is not allowed to read the user's authblob") 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: return getError(rc.FORBIDDEN, "This plugin is not allowed to write the user's authblob") if not request.data.has_key('authblob'): return getError(rc.BAD_REQUEST, "To change the user's auth blob you actually need to provide one") 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: return getError(rc.BAD_REQUEST, "This plugin does not support unique auth blobs, therefore we can't identify a user uniquely by their authblob") if not request.GET.has_key('authblob') or request.GET['authblob'] == '': return rc.BAD_REQUEST user = getUserFromAuthblob(request.GET['authblob'], request.plugin) if user: return user 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', }