452 lines
15 KiB
Python
452 lines
15 KiB
Python
# This file is part of k4ever, a point-of-sale system
|
|
# Contact............ <k4ever@lists.someserver.de>
|
|
# 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 'type' in request.GET:
|
|
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 <buyable.models.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 <buyable.models.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 getError(rc.NOT_FOUND, "A Buyable with that id does not exist.")
|
|
|
|
# parse post data
|
|
deposit = getInt(request.data, 'deposit', self.BUY_ITEM)
|
|
amount = getInt(request.data, 'amount', 1)
|
|
if amount < 1:
|
|
return getError(rc.BAD_REQUEST, "Buying zero or negative amounts is not supported.")
|
|
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 getError(rc.BAD_REQUEST, "Either this item has no deposit or you specified an invalid deposit buy mode.")
|
|
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 {'success': True, 'balance': request.user.get_profile().balance}
|
|
|
|
def bulkBuy(self, request):
|
|
"""Buy a :class:`Buyable <buyable.models.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 "items" in request.data and not "deposits" in request.data:
|
|
return getError(rc.BAD_REQUEST, "You need to specify either items or deposits (or both).")
|
|
|
|
if not "items" in request.data:
|
|
request.data['items'] = []
|
|
if not "deposits" in request.data:
|
|
request.data['deposits'] = []
|
|
|
|
itemList = []
|
|
try:
|
|
if not isinstance(request.data['items'], Iterable):
|
|
raise TypeError()
|
|
itemList += request.data['items']
|
|
if 'items' in request.data:
|
|
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 item in ids:
|
|
try:
|
|
ids[item] = Buyable.objects.get(id=item)
|
|
except Buyable.DoesNotExist:
|
|
return getError(rc.NOT_FOUND, "The item with the id '%s' could not be found" % (item,))
|
|
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 {'success': True, 'balance': request.user.get_profile().balance}
|
|
|
|
|
|
class BuyableTypeHandler(BaseHandler):
|
|
"""Handler for listing all :class:`BuyableType <buyable.models.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:
|
|
return getError(rc.BAD_REQUEST, "Something is seriously broken, django urls SHOULD have parsed out the non-number thingie.")
|
|
|
|
if thumbSize not in THUMB_SIZES:
|
|
return getError(rc.BAD_REQUEST, "The requested thumbnailsize is not available.")
|
|
|
|
item = None
|
|
try:
|
|
item = Buyable.objects.get(id=itemId)
|
|
except Buyable.DoesNotExist:
|
|
return getError(rc.NOT_FOUND, "The item with the id '%s' could not be found" % (itemId,))
|
|
|
|
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 getError(rc.BAD_REQUEST, "Please supply a positive number of entries.")
|
|
|
|
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 {'success': True, 'balance': request.user.get_profile().balance}
|
|
|
|
class TransactionTypeHandler(BaseHandler):
|
|
"""Handler for :class:`Transaction Types <transaction.models.TransactionType>`
|
|
|
|
Supplies a list of Transaction Types
|
|
"""
|
|
allowed_methods = ('GET',)
|
|
model = TransactionType
|
|
|
|
class TransactionVirtualHandler(BaseHandler):
|
|
""" Handler for :class:`Virtual Transaction <transaction.models.VirtualTransaction>`
|
|
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 getError(rc.BAD_REQUEST, "Please supply a positive number of entries.")
|
|
|
|
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 {'success': True, 'balance': request.user.get_profile().balance}
|
|
|
|
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 'authblob' in request.data:
|
|
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 {'success': True}
|
|
|
|
class AuthUserHandler(BaseHandler):
|
|
""" Handler for mapping an authblob to a user
|
|
|
|
This handler is only available to plugins and only if
|
|
:attr:`unique authblob <main.models.Plugin.uniqueAuthblob>`
|
|
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 request.GET.get('authblob', '') == '':
|
|
return getError(rc.BAD_REQUEST, "Authblob was empty.")
|
|
|
|
user = getUserFromAuthblob(request.GET['authblob'], request.plugin)
|
|
if user:
|
|
return user
|
|
return getError(rc.NOT_FOUND, "A user with that authblob could not be 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',
|
|
}
|
|
|