Added a *lot* of documentation

This commit is contained in:
seba 2011-10-06 18:09:35 +02:00
parent 7850b322c5
commit 4fbd3c6651
14 changed files with 106 additions and 67 deletions

View File

@ -29,7 +29,7 @@ Open for discussion:
- 15, besser 20:Man sucht auf ja nach etwas und will sich nicht totklicken ~~~TKroenert - 15, besser 20:Man sucht auf ja nach etwas und will sich nicht totklicken ~~~TKroenert
Konrad: Konrad:
Abmeldenutton rechts oder rot? Abmeldebutton rechts oder rot?
- die liste der zu einkaufenden items ist doof :( - die liste der zu einkaufenden items ist doof :(
- /store/history/ ist noch kaputt + zeit unformatiert - /store/history/ ist noch kaputt + zeit unformatiert
- /transaction/ sehen die gemachten transactions noch nicht so cool aus - /transaction/ sehen die gemachten transactions noch nicht so cool aus

View File

@ -4,16 +4,17 @@ from piston.utils import rc
from main.models import Plugin, PluginPermission from main.models import Plugin, PluginPermission
def manglePluginPerms(apiFunc): def manglePluginPerms(apiFunc):
""" Changes to a given user when the authenticated user is an plugin """ Changes to a given user when the authenticated user is an plugin.
When the user which called the apifunc is a plugin this function
goes through the following steps:
- searches the user it should change to
- checks if this user allowed the plugin to "speak for him"
- change the request so it looks like the user called himself
- add an plugin_user entry containing the previous request user
When the user which called the apifunc is an plugin this function This decorator is intended to be used with django piston, so on error
goes through the following steps: it will return the appropriate rc.* values.
- searches the user it should change to
- checks if this user allowed the plugin to "speak for him"
- change the request so it looks like the user called himself
- add an plugin_user entry containing the previous request user
This decorator is intended to be used with django piston, so on error
it will return the appropriate rc.* values.
""" """
@wraps(apiFunc) @wraps(apiFunc)

View File

@ -1,12 +1,14 @@
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
def getInt(d, key, default): def getInt(d, key, default):
""" Helper for dict.get to return the default on error. """
try: try:
return int(d.get(key, default)) return int(d.get(key, default))
except ValueError: except ValueError:
return default return default
def getDecimal(d, key, default): def getDecimal(d, key, default):
""" Helper for dict.get to return the default on error. """
try: try:
return Decimal(d.get(key, default)) return Decimal(d.get(key, default))
except InvalidOperation: except InvalidOperation:

View File

@ -4,9 +4,13 @@ from piston.authentication import HttpBasicAuthentication
from api2.authentication import DjangoAuthentication, MultiAuthentication from api2.authentication import DjangoAuthentication, MultiAuthentication
from api2.handlers import * from api2.handlers import *
# taken from
# http://www.robertshady.com/content/creating-very-basic-api-using-python-django-and-piston
class CsrfExemptResource( Resource ): class CsrfExemptResource( Resource ):
""" Except a :class:`Resource` from djangos CSRF-Framework.
This idea is taken from
http://www.robertshady.com/content/creating-very-basic-api-using-python-django-and-piston
"""
def __init__( self, handler, authentication = None ): def __init__( self, handler, authentication = None ):
super( CsrfExemptResource, self ).__init__( handler, authentication ) super( CsrfExemptResource, self ).__init__( handler, authentication )
self.csrf_exempt = getattr( self.handler, 'csrf_exempt', True ) self.csrf_exempt = getattr( self.handler, 'csrf_exempt', True )

View File

@ -29,6 +29,7 @@ class Buyable(models.Model):
return self.deposit > Decimal(0) return self.deposit > Decimal(0)
def createPurchase(self, isDeposit=False): def createPurchase(self, isDeposit=False):
""" Creates a :class:`Purchase` containing this :class:`Buyable`. """
p = Purchase() p = Purchase()
if isDeposit: if isDeposit:
p.price = self.deposit p.price = self.deposit
@ -47,11 +48,7 @@ class Buyable(models.Model):
return item return item
class Order(models.Model): class Order(models.Model):
""" Represents an order by the user. """ Represents an order by the user, consists of several :class:`Purchases <Purchase>`. """
"""
An Order object is referenced by all :class:`Purchases <Purchase>`
user = models.ForeignKey(User) user = models.ForeignKey(User)
price = models.DecimalField(max_digits=8, decimal_places=2) price = models.DecimalField(max_digits=8, decimal_places=2)
dateTime = models.DateTimeField() dateTime = models.DateTimeField()
@ -93,7 +90,7 @@ class Order(models.Model):
class Purchase(models.Model): class Purchase(models.Model):
""" Represents a :class:``""" """ Represents a :class:`Buyable` in a :class:`Order`"""
order = models.ForeignKey(Order) order = models.ForeignKey(Order)
price = models.DecimalField(max_digits=8, decimal_places=2) price = models.DecimalField(max_digits=8, decimal_places=2)
isDeposit = models.BooleanField() isDeposit = models.BooleanField()
@ -101,6 +98,7 @@ class Purchase(models.Model):
@staticmethod @staticmethod
def create(order, buyable, isDeposit=False): def create(order, buyable, isDeposit=False):
""" Create a Purchase from a buyable. Still needs to be saved. """
p = Purchase() p = Purchase()
p.order = order p.order = order
p.isDeposit = isDeposit p.isDeposit = isDeposit
@ -115,13 +113,4 @@ class Purchase(models.Model):
def __unicode__(self): def __unicode__(self):
return "%s%s" % (self.buyable.name, (self.isDeposit and " (deposit)" or "")) return "%s%s" % (self.buyable.name, (self.isDeposit and " (deposit)" or ""))
# def save(self, *args, **kwargs):
# profile = self.user.get_profile()
# if self.id == None:
# # new item, get it!
# profile.balance -= self.price
# profile.save()
# super(Purchase, self).save(*args, **kwargs)

View File

@ -54,6 +54,7 @@ def buyItem(request, itemid, buymode=""):
@login_required @login_required
def boughtItem(request, orderid): def boughtItem(request, orderid):
""" View which shows what was bought."""
error = None error = None
try: try:
item = Order.objects.get(id=orderid) item = Order.objects.get(id=orderid)
@ -66,6 +67,7 @@ def boughtItem(request, orderid):
@login_required @login_required
def history(request): def history(request):
""" Show the users previous orders. """
hist = Order.objects.filter(user=request.user.id).order_by("-dateTime") hist = Order.objects.filter(user=request.user.id).order_by("-dateTime")
paginator = Paginator(hist, 10, orphans=3) paginator = Paginator(hist, 10, orphans=3)

View File

@ -1,22 +1,24 @@
API API
=== ===
- explain a bit how piston/REST works
- how to access the read/write/put/create functions
- what this api does and what not
K4ever has a REST-like API. This means every URL represents an object which has K4ever has a REST-like API. This means every URL represents an object which has
four functions: `read`, `create`, `update` and `delete`. These functions are mapped four functions: ``read``, ``create``, ``update`` and ``delete``. These functions are mapped
to the `HTTP`-Methods `GET`, `POST`, `PUT` and `DELETE`. Most of the functionality to the ``HTTP``-Methods ``GET``, ``POST``, ``PUT`` and ``DELETE``. Most of the functionality
uses only `GET` and `POST`, so everything is accessible via `wget`. uses only ``GET`` and ``POST``, so everything is accessible via ``wget``.
The API enables you to list available items, buy them and transtact money to your The API enables you to list available items, buy them and transtact money to your
accout. It also has a *plugin*-concept. Plugins can be allowed by other user accout. To get an idea what functions are available and how they are used scroll down
to the URLs section. Some URLs take arguments. For a ``GET``-request you can attach
them normaly, e.g. `/buyable/item/?barcode=4029764001807`. For a ``POST``, ``PUT`` or
``DELETE`` request the parameters have to be put in the ``HTTP``-requests body. They
can be provided url, JSON or YAML-encoded - dealers choice.
Authentication can be done either per HTTP Basic Auth or, for AJAX-Requests, per
cookie (as in "logged in via the webinterface"). Note that most ``HTTP``-libraries
like pythons urllib2 (or as a tool ``wget``) don't send authenticationdata except
when being challenged with a ``HTTP 401 - Authentication required``, meaning that
every API-call requires the library to make two requests. In general this behaviour
can be turned off.
Authentication can be done either per HTTP Basic Auth or for AJAX-Requests per
cookie. The
k4evers API
Plugins Plugins
-------------- --------------
@ -25,7 +27,24 @@ Plugins
- when does a plugin need an user? - when does a plugin need an user?
- how to change user names - how to change user names
k4evers API also has a *plugin*-concept. :class:`Plugins <main.models.Plugin>`
can be allowed by users to buy items on their behalf. To do this the user
has to allow the plugin via the webinterface. A :class:`PluginPermission
<main.models.PluginPermission>` object will be created to keep track of this.
Each :class:`PluginPermission <main.models.PluginPermission>` object has an
:attr:`authblob <main.models.PluginPermission.authblob>` in which the plugin
can save or access arbitrary data on a per-user basis. The plugin has several
configuration options for the `authblob`. They control read/write access for
plugin (via API) or user (via webinterface) to the `authblob` and if the
`authblob` has to be unique. The latter one can be useful if the `authblob`
is needed to be primary key and identify exactly one user
(see also :ref:`apilink <authblob-authentication>`).
To do something as another user the plugin can, if allowed by the user, add
the `user`-parameter with the **username** (not the id) to all its requests.
E.g. to get the account balance of the user *cat* this would look like
`user/account/?user=cat`.
URLs URLs
---- ----
@ -111,6 +130,9 @@ to the handler or the responsible method for more documentation or code.
:func:`POST <api2.handlers.AuthBlobHandler.create>` :func:`POST <api2.handlers.AuthBlobHandler.create>`
Set authblob if allowed Set authblob if allowed
.. _authblob-authentication:
:class:`user/ <api2.handlers.AuthUserHandler>` :class:`user/ <api2.handlers.AuthUserHandler>`
:func:`GET <api2.handlers.AuthUserHandler.read>` :func:`GET <api2.handlers.AuthUserHandler.read>`
get user by authblob string - this function works only when plugin get user by authblob string - this function works only when plugin
@ -132,20 +154,21 @@ Handler
.. automodule:: api2.handlers .. automodule:: api2.handlers
:members: :members:
Plugin Models
-------------
.. autoclass:: main.models.Plugin
:members:
.. autoclass:: main.models.PluginPermission
:members:
Decorators
----------
.. automodule:: api2.decorators
:members:
Examples Examples
-------- --------
- how to use the api - how to use the api
- examples with... wget.. python-rest? - examples with... wget.. python-rest?
As normal user
^^^^^^^^^^^^^^
.. note:: there will be cat content
wget
""""
wget part
As a plugin
^^^^^^^^^^^

View File

@ -1,5 +0,0 @@
from django_auth_ldap.backend import LDAPBackend
CustomLDAPBackend(LDAPBackend):
def populate_user(username):

View File

@ -30,6 +30,7 @@ class CurrencyInput (forms.TextInput):
class CurrencyField (forms.RegexField): class CurrencyField (forms.RegexField):
""" Represents a currency field for django forms... or something. """
widget = CurrencyInput widget = CurrencyInput
currencyRe = re.compile(r'^[0-9]{1,5}([,\.][0-9][0-9]?)?$') currencyRe = re.compile(r'^[0-9]{1,5}([,\.][0-9][0-9]?)?$')

View File

@ -4,6 +4,7 @@ from django.contrib.auth.models import User
from decimal import Decimal from decimal import Decimal
class UserProfile(models.Model): class UserProfile(models.Model):
""" Contains data for a user, especially the account balance. """
user = models.ForeignKey(User, unique=True) user = models.ForeignKey(User, unique=True)
balance = models.DecimalField(max_digits=9, decimal_places=2, default=Decimal(0)) balance = models.DecimalField(max_digits=9, decimal_places=2, default=Decimal(0))
@ -11,6 +12,7 @@ class UserProfile(models.Model):
return "%s (Kontostand: %s)" % (self.user ,self.balance) return "%s (Kontostand: %s)" % (self.user ,self.balance)
def createUserProfile(sender, instance, created, **kwargs): def createUserProfile(sender, instance, created, **kwargs):
""" Hook to create a new :class:`UserProfile` if the user is created. """
if created: if created:
profile = UserProfile() profile = UserProfile()
profile.user = instance profile.user = instance
@ -19,6 +21,19 @@ def createUserProfile(sender, instance, created, **kwargs):
post_save.connect(createUserProfile, sender=User) post_save.connect(createUserProfile, sender=User)
class Plugin(models.Model): class Plugin(models.Model):
""" This Model contains a plugin and its configuration.
A Plugin consists of its own information (name, author, version
and descrption) which is displayed for the user on the plugin
selection page, a configuration what a plugin is allowed to do
and what not and an own user for authentication against the
API.
:attr:`uniqueAuthblob` is used if the :class:`Plugin` has to uniquely
identify an user by his/her :attr:`authblob <PluginPermission.authblob>`.
The other attributes are used for plugin/user read/write access to the
authblob.
"""
user = models.ForeignKey(User, unique=True) user = models.ForeignKey(User, unique=True)
# plugin info # plugin info
@ -39,8 +54,15 @@ class Plugin(models.Model):
return self.name return self.name
class PluginPermission(models.Model): class PluginPermission(models.Model):
""" States that a user allows access to his/her account to a :class:`Plugin`.
The :attr:`authblob` can be used by the Plugin to store
authentication data, e.g. a barcode or hashed password.
"""
user = models.ForeignKey(User) user = models.ForeignKey(User)
plugin = models.ForeignKey('Plugin') plugin = models.ForeignKey('Plugin')
#: authblob which holds arbitrary authentication data.
authblob = models.TextField(default='') authblob = models.TextField(default='')
def __unicode__(self): def __unicode__(self):

View File

@ -19,10 +19,12 @@ def startpage(request):
return render_to_response("main/startpage.html", {'allMost' : allMost,'usersMost': usersMost, 'usersLast' : usersLast[:12]}, RequestContext(request)) return render_to_response("main/startpage.html", {'allMost' : allMost,'usersMost': usersMost, 'usersLast' : usersLast[:12]}, RequestContext(request))
def register(request): def register(request):
""" The "no registration available" page... """
return render_to_response("registration/register.html", RequestContext(request)) return render_to_response("registration/register.html", RequestContext(request))
def getPluginDict(request): def getPluginDict(request):
""" Generate a dict containing the users plugin information. """
plugins = Plugin.objects.all() plugins = Plugin.objects.all()
allowed = Plugin.objects.filter(pluginpermission__user=request.user) allowed = Plugin.objects.filter(pluginpermission__user=request.user)
unallowed = Plugin.objects.exclude(pluginpermission__user=request.user) unallowed = Plugin.objects.exclude(pluginpermission__user=request.user)
@ -32,10 +34,12 @@ def getPluginDict(request):
@login_required @login_required
def settings(request): def settings(request):
""" Render settings page. """
return render_to_response("settings/settings.html", getPluginDict(request), RequestContext(request)) return render_to_response("settings/settings.html", getPluginDict(request), RequestContext(request))
@login_required @login_required
def pluginPermission(request, method, pluginId): def pluginPermission(request, method, pluginId):
""" View to edit the users :class:`Plugin` permissions. """
plugin = None plugin = None
try: try:
plugin = Plugin.objects.get(id=pluginId) plugin = Plugin.objects.get(id=pluginId)
@ -65,6 +69,7 @@ def pluginPermission(request, method, pluginId):
@login_required @login_required
def pluginAuthblob(request, pluginId): def pluginAuthblob(request, pluginId):
""" View to edit the users :attr:`authblob <PluginPermission.authblob>`. """
if request.method != "POST": if request.method != "POST":
return HttpResponseRedirect("/user/settings/") return HttpResponseRedirect("/user/settings/")
plugin = None plugin = None

View File

@ -3,6 +3,7 @@ from models import Transaction
from main.fields import CurrencyField from main.fields import CurrencyField
class TransactionForm(forms.ModelForm): class TransactionForm(forms.ModelForm):
""" ModelForm for :class:`Transactions <Transaction>` with a currency field """
amount = CurrencyField(label='Betrag') amount = CurrencyField(label='Betrag')
class Meta: class Meta:
model = Transaction model = Transaction

View File

@ -2,6 +2,9 @@ from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
class TransactionType(models.Model): class TransactionType(models.Model):
""" Type for a :class:`Transaction`. States how the money has
been added to the account and if somebody needs to check
if it was payed. """
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
needsCheck = models.BooleanField(default=True) needsCheck = models.BooleanField(default=True)
@ -9,6 +12,7 @@ class TransactionType(models.Model):
return unicode(self.name) return unicode(self.name)
class Transaction(models.Model): class Transaction(models.Model):
"""Represents a money transaction into the users account. """
user = models.ForeignKey(User) user = models.ForeignKey(User)
transactionType = models.ForeignKey(TransactionType, verbose_name='Typ') transactionType = models.ForeignKey(TransactionType, verbose_name='Typ')
dateTime = models.DateTimeField() dateTime = models.DateTimeField()

View File

@ -8,6 +8,7 @@ import datetime
@login_required @login_required
def overview(request): def overview(request):
""" Creates an overview over the users trasnactions, also handles adding money. """
history = Transaction.objects.filter(user=request.user).order_by("-dateTime") history = Transaction.objects.filter(user=request.user).order_by("-dateTime")
transacted = False transacted = False
error = False error = False
@ -26,14 +27,3 @@ def overview(request):
form = TransactionForm() form = TransactionForm()
return render_to_response("transaction/overview.html", {'history': history, 'form': form, 'transacted': transacted, 'error': error}, RequestContext(request)) return render_to_response("transaction/overview.html", {'history': history, 'form': form, 'transacted': transacted, 'error': error}, RequestContext(request))
@login_required
def transact(request):
if request.method == 'POST':
return render_to_response("transaction/transfered.html", RequestContext(request))
else:
return HttpResponseRedirect("/transaction/")
#@kassenwart_required
#def checkTransfers(request):
# transfers = Transaction.objects.filter(checked=False).order_by("dateTime")
# return render_to_response("transaction/uncheckedTransfers.html", {'transfers' : tranfers}, RequestContext(request))