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
Konrad:
Abmeldenutton rechts oder rot?
Abmeldebutton rechts oder rot?
- die liste der zu einkaufenden items ist doof :(
- /store/history/ ist noch kaputt + zeit unformatiert
- /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
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 an 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
This decorator is intended to be used with django piston, so on error
it will return the appropriate rc.* values.
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
This decorator is intended to be used with django piston, so on error
it will return the appropriate rc.* values.
"""
@wraps(apiFunc)

View File

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

View File

@ -4,9 +4,13 @@ from piston.authentication import HttpBasicAuthentication
from api2.authentication import DjangoAuthentication, MultiAuthentication
from api2.handlers import *
# taken from
# http://www.robertshady.com/content/creating-very-basic-api-using-python-django-and-piston
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 ):
super( CsrfExemptResource, self ).__init__( handler, authentication )
self.csrf_exempt = getattr( self.handler, 'csrf_exempt', True )

View File

@ -29,6 +29,7 @@ class Buyable(models.Model):
return self.deposit > Decimal(0)
def createPurchase(self, isDeposit=False):
""" Creates a :class:`Purchase` containing this :class:`Buyable`. """
p = Purchase()
if isDeposit:
p.price = self.deposit
@ -47,11 +48,7 @@ class Buyable(models.Model):
return item
class Order(models.Model):
""" Represents an order by the user.
"""
An Order object is referenced by all :class:`Purchases <Purchase>`
""" Represents an order by the user, consists of several :class:`Purchases <Purchase>`. """
user = models.ForeignKey(User)
price = models.DecimalField(max_digits=8, decimal_places=2)
dateTime = models.DateTimeField()
@ -93,7 +90,7 @@ class Order(models.Model):
class Purchase(models.Model):
""" Represents a :class:``"""
""" Represents a :class:`Buyable` in a :class:`Order`"""
order = models.ForeignKey(Order)
price = models.DecimalField(max_digits=8, decimal_places=2)
isDeposit = models.BooleanField()
@ -101,6 +98,7 @@ class Purchase(models.Model):
@staticmethod
def create(order, buyable, isDeposit=False):
""" Create a Purchase from a buyable. Still needs to be saved. """
p = Purchase()
p.order = order
p.isDeposit = isDeposit
@ -116,12 +114,3 @@ class Purchase(models.Model):
def __unicode__(self):
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
def boughtItem(request, orderid):
""" View which shows what was bought."""
error = None
try:
item = Order.objects.get(id=orderid)
@ -66,6 +67,7 @@ def boughtItem(request, orderid):
@login_required
def history(request):
""" Show the users previous orders. """
hist = Order.objects.filter(user=request.user.id).order_by("-dateTime")
paginator = Paginator(hist, 10, orphans=3)

View File

@ -1,22 +1,24 @@
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
four functions: `read`, `create`, `update` and `delete`. These functions are mapped
to the `HTTP`-Methods `GET`, `POST`, `PUT` and `DELETE`. Most of the functionality
uses only `GET` and `POST`, so everything is accessible via `wget`.
four functions: ``read``, ``create``, ``update`` and ``delete``. These functions are mapped
to the ``HTTP``-Methods ``GET``, ``POST``, ``PUT`` and ``DELETE``. Most of the functionality
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
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
--------------
@ -25,7 +27,24 @@ Plugins
- when does a plugin need an user?
- 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
----
@ -111,6 +130,9 @@ to the handler or the responsible method for more documentation or code.
:func:`POST <api2.handlers.AuthBlobHandler.create>`
Set authblob if allowed
.. _authblob-authentication:
:class:`user/ <api2.handlers.AuthUserHandler>`
:func:`GET <api2.handlers.AuthUserHandler.read>`
get user by authblob string - this function works only when plugin
@ -132,20 +154,21 @@ Handler
.. automodule:: api2.handlers
:members:
Plugin Models
-------------
.. autoclass:: main.models.Plugin
:members:
.. autoclass:: main.models.PluginPermission
:members:
Decorators
----------
.. automodule:: api2.decorators
:members:
Examples
--------
- how to use the api
- 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):
""" Represents a currency field for django forms... or something. """
widget = CurrencyInput
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
class UserProfile(models.Model):
""" Contains data for a user, especially the account balance. """
user = models.ForeignKey(User, unique=True)
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)
def createUserProfile(sender, instance, created, **kwargs):
""" Hook to create a new :class:`UserProfile` if the user is created. """
if created:
profile = UserProfile()
profile.user = instance
@ -19,6 +21,19 @@ def createUserProfile(sender, instance, created, **kwargs):
post_save.connect(createUserProfile, sender=User)
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)
# plugin info
@ -39,8 +54,15 @@ class Plugin(models.Model):
return self.name
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)
plugin = models.ForeignKey('Plugin')
#: authblob which holds arbitrary authentication data.
authblob = models.TextField(default='')
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))
def register(request):
""" The "no registration available" page... """
return render_to_response("registration/register.html", RequestContext(request))
def getPluginDict(request):
""" Generate a dict containing the users plugin information. """
plugins = Plugin.objects.all()
allowed = Plugin.objects.filter(pluginpermission__user=request.user)
unallowed = Plugin.objects.exclude(pluginpermission__user=request.user)
@ -32,10 +34,12 @@ def getPluginDict(request):
@login_required
def settings(request):
""" Render settings page. """
return render_to_response("settings/settings.html", getPluginDict(request), RequestContext(request))
@login_required
def pluginPermission(request, method, pluginId):
""" View to edit the users :class:`Plugin` permissions. """
plugin = None
try:
plugin = Plugin.objects.get(id=pluginId)
@ -65,6 +69,7 @@ def pluginPermission(request, method, pluginId):
@login_required
def pluginAuthblob(request, pluginId):
""" View to edit the users :attr:`authblob <PluginPermission.authblob>`. """
if request.method != "POST":
return HttpResponseRedirect("/user/settings/")
plugin = None

View File

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

View File

@ -2,6 +2,9 @@ from django.db import models
from django.contrib.auth.models import User
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)
needsCheck = models.BooleanField(default=True)
@ -9,6 +12,7 @@ class TransactionType(models.Model):
return unicode(self.name)
class Transaction(models.Model):
"""Represents a money transaction into the users account. """
user = models.ForeignKey(User)
transactionType = models.ForeignKey(TransactionType, verbose_name='Typ')
dateTime = models.DateTimeField()

View File

@ -8,6 +8,7 @@ import datetime
@login_required
def overview(request):
""" Creates an overview over the users trasnactions, also handles adding money. """
history = Transaction.objects.filter(user=request.user).order_by("-dateTime")
transacted = False
error = False
@ -26,14 +27,3 @@ def overview(request):
form = TransactionForm()
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))