# 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 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 <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 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 :
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 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 { ' 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 not request . GET . has_key ( ' authblob ' ) or request . 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 ' ,
}