[Assorted-commits] SF.net SVN: assorted:[1522] pitch-in/trunk
Brought to you by:
yangzhang
From: <yan...@us...> - 2009-11-27 06:35:38
|
Revision: 1522 http://assorted.svn.sourceforge.net/assorted/?rev=1522&view=rev Author: yangzhang Date: 2009-11-27 06:35:23 +0000 (Fri, 27 Nov 2009) Log Message: ----------- - allow arbitrary contribution inputs, not just (integer) numbers - using django forms - migrated to django 1.1.1 - perform update from transaction - styled errors to be prettier - reorganized views.py Modified Paths: -------------- pitch-in/trunk/README pitch-in/trunk/src/pitchin/main.py pitch-in/trunk/src/pitchin/pitchin/templates/base.html pitch-in/trunk/src/pitchin/pitchin/templates/pool.html pitch-in/trunk/src/pitchin/pitchin/views.py pitch-in/trunk/src/pitchin/static/default.css Modified: pitch-in/trunk/README =================================================================== --- pitch-in/trunk/README 2009-11-26 09:57:59 UTC (rev 1521) +++ pitch-in/trunk/README 2009-11-27 06:35:23 UTC (rev 1522) @@ -35,3 +35,9 @@ - new cycle syntax: cycle '0' '1' - autoescape - for key, value in collection + +Related +------- + +- [ChipIn](http://www.chipin.com/) +- [KickStarter](http://www.kickstarter.com/) Modified: pitch-in/trunk/src/pitchin/main.py =================================================================== --- pitch-in/trunk/src/pitchin/main.py 2009-11-26 09:57:59 UTC (rev 1521) +++ pitch-in/trunk/src/pitchin/main.py 2009-11-27 06:35:23 UTC (rev 1522) @@ -1,3 +1,5 @@ +# main.py for Django 1.1: <http://tekpizza.blogspot.com/2009/11/mainpy-for-django-11-in-google-app.html> + import logging, os # Google App Engine imports. @@ -6,6 +8,9 @@ # Must set this env var before importing any part of Django os.environ['DJANGO_SETTINGS_MODULE'] = 'pitchin.settings' +from google.appengine.dist import use_library +use_library('django', '1.1') + # Force Django to reload its settings. from django.conf import settings settings._target = None @@ -19,14 +24,13 @@ def log_exception(*args, **kwds): logging.exception('Exception in request:') +dispatcher = django.dispatch.dispatcher.Signal() + # Log errors. -django.dispatch.dispatcher.connect( - log_exception, django.core.signals.got_request_exception) +dispatcher.connect(log_exception, django.core.signals.got_request_exception) # Unregister the rollback event handler. -django.dispatch.dispatcher.disconnect( - django.db._rollback_on_exception, - django.core.signals.got_request_exception) +dispatcher.disconnect(django.db._rollback_on_exception, django.core.signals.got_request_exception) def main(): # Create a Django application for WSGI. Modified: pitch-in/trunk/src/pitchin/pitchin/templates/base.html =================================================================== --- pitch-in/trunk/src/pitchin/pitchin/templates/base.html 2009-11-26 09:57:59 UTC (rev 1521) +++ pitch-in/trunk/src/pitchin/pitchin/templates/base.html 2009-11-27 06:35:23 UTC (rev 1522) @@ -16,8 +16,8 @@ {% block body %}{% endblock %} <div style="margin-top: 50px"> <small> - <strong>PitchIn</strong> © 2009 <a href="http://www.mit.edu/~y_z/">YZ</a> | - <a href="http://assorted.sf.net/pitch-in/">AGPL Source</a> <!--| + <strong>PitchIn</strong> © 2009 Yang Zhang, Richard West <!--<a href="http://www.mit.edu/~y_z/">YZ</a> | + <a href="http://assorted.sf.net/pitch-in/">AGPL Source</a> --><!--| <a href="sponsorship">Sponsorship</a>--> </small> </div> Modified: pitch-in/trunk/src/pitchin/pitchin/templates/pool.html =================================================================== --- pitch-in/trunk/src/pitchin/pitchin/templates/pool.html 2009-11-26 09:57:59 UTC (rev 1521) +++ pitch-in/trunk/src/pitchin/pitchin/templates/pool.html 2009-11-27 06:35:23 UTC (rev 1522) @@ -16,7 +16,23 @@ the following information. </p> <form action="/pool" method="post"> - <p class="error">{{error}}</p> + <div class="errors"> + {% comment %} + <ul> + {% for field, errors in form.errors.iteritems %} + <li>{{form.field.label}}: {{errors}}</li> + {% endfor %} + </ul> + {% endcomment %} + {{form.errors}} + </div> + {% comment %} + {% for field in form %} + {{field}} + {% endfor %} + {% endcomment %} + <div class="hideerrors">{{form}}</div> + <!-- <div> <input type="hidden" name="key" value="{{key|urlencode}}"/> </div> @@ -29,9 +45,10 @@ <div> <label> Contribution: - <input type="text" name="amount" value="{{amount|escape}}"/> + <input type="text" name="contrib" value="{{contrib|escape}}"/> </label> </div> + --> <div><input type="submit" value="Enter the pool!"/></div> </form> {% endblock %} Modified: pitch-in/trunk/src/pitchin/pitchin/views.py =================================================================== --- pitch-in/trunk/src/pitchin/pitchin/views.py 2009-11-26 09:57:59 UTC (rev 1521) +++ pitch-in/trunk/src/pitchin/pitchin/views.py 2009-11-27 06:35:23 UTC (rev 1522) @@ -1,9 +1,30 @@ from django.http import HttpResponseRedirect from django.shortcuts import render_to_response +from django import forms +from django.forms.util import ErrorList from google.appengine.ext import db from pitchin.models import Pool import cPickle, cgi +# +# General utilities. +# + +def txn(f): + return ( lambda *args, **kwargs: + db.run_in_transaction_custom_retries(3, f, *args, **kwargs) ) + +def isfloat(x): + try: float(x) + except ValueError: return False + else: return True + +def mean(xs): return sum(xs) / len(xs) + +# +# Application code. +# + class invalid_pool(Exception): pass def getPool(request): @@ -13,18 +34,64 @@ def getContribs(pool): return cPickle.loads(str(pool.contribs)) def serContribs(contribs): return cPickle.dumps(contribs, 2) def aggContribs(contribs): - if len(contribs) == 0: + nums = map(float, filter(isfloat, contribs.itervalues())) + if len(nums) == 0 or len(nums) < len(contribs) / 2: return [] else: - return [('Max total', sum(contribs.itervalues())), - ('If everyone paid the minimum', len(contribs) * min(contribs.itervalues()))] + return [ ( 'Total', '%.2f' % sum(nums) ), + ( 'Average', '%.2f' % mean(nums) ), + ( 'If everyone paid the minimum', + '%.2f' % (len(contribs) * min(nums)) ) ] class invalid_submit(Exception): pass def validate(pred, msg): if not pred: raise invalid_submit(msg) -### Views +# +# Forms. +# +class CustomErrorList(ErrorList): + def __unicode__(self): return self.as_divs() + def as_divs(self): return u'' + +class ContactForm(forms.Form): + key = forms.CharField(required = True, + max_length = 100, + widget = forms.HiddenInput) + name = forms.CharField(required = True, max_length = 100, + label = 'Name', + widget = forms.TextInput(attrs = {})) + contrib = forms.CharField(required = True, max_length = 100, label = 'Contribution') + def __init__(self, *args, **kwargs): + #kwargs['error_class'] = CustomErrorList + forms.Form.__init__(self, *args, **kwargs) + +# +# Common code for views. +# + +def bad_pool(request): + return render_to_response('main.html', dict(generror = 'Pool not found/invalid pool ID')) + +def view(request, form): + if 'key' not in request.REQUEST: + return HttpResponseRedirect('/') + try: + pool = getPool(request) + return render_to_response('pool.html', + dict(descrip = pool.descrip, + contribs = getContribs(pool).items(), + aggcontribs = aggContribs(getContribs(pool)), + key = request.REQUEST['key'], + form = form)) + except invalid_pool: + return bad_pool(request) + +# +# Views. +# + def main(request): return render_to_response('main.html') @@ -38,45 +105,28 @@ return render_to_response('created.html', {'key': pool.key()}) def pool(request): - if request.method == 'GET': return view(request) + if request.method == 'GET': return view(request, ContactForm(initial = request.REQUEST)) else: return update(request) -def view(request, **params): - if 'key' not in request.REQUEST: - return HttpResponseRedirect('/') - try: - pool = getPool(request) - return render_to_response('pool.html', - dict(descrip = pool.descrip, - contribs = getContribs(pool).iteritems(), - aggcontribs = aggContribs(getContribs(pool)), - key = request.REQUEST['key'], - **params)) - except invalid_pool: - return bad_pool(request) - def update(request): try: - validate(request.REQUEST['name'].strip() != '', - 'You must enter a name.') - try: amount = int(request.REQUEST['amount']) - except ValueError: raise invalid_submit('You must enter an integer dollar amount.') + form = ContactForm(request.REQUEST) + if not form.is_valid(): raise invalid_submit(form.errors) + cleaned = form.clean_data except invalid_submit, ex: - return view(request, error = ex, name = request.REQUEST['name'], amount = request.REQUEST['amount']) + return view(request, form) else: - try: + @txn + def f(): pool = getPool(request) - except invalid_pool: - return bad_pool(request) - else: contribs = getContribs(pool) - contribs[request.REQUEST['name']] = amount + contribs[cleaned['name']] = cleaned['contrib'] pool.contribs = cPickle.dumps(contribs) pool.put() - return HttpResponseRedirect('/pool?key=%s' % pool.key()) + return pool + try: pool = f() + except invalid_pool: return bad_pool(request) + else: return HttpResponseRedirect('/pool?key=%s' % pool.key()) def sponsorship(request): return render_to_response('sponsorship.html') - -def bad_pool(request): - return render_to_response('main.html', dict(generror = 'Pool not found/invalid pool ID')) Modified: pitch-in/trunk/src/pitchin/static/default.css =================================================================== --- pitch-in/trunk/src/pitchin/static/default.css 2009-11-26 09:57:59 UTC (rev 1521) +++ pitch-in/trunk/src/pitchin/static/default.css 2009-11-27 06:35:23 UTC (rev 1522) @@ -7,7 +7,16 @@ .boxtop { background: url(tr.png) no-repeat top right; } .header { text-align: center; width: 100%; } .body { margin: 0 30px; text-align: center; } -.error { color: red; } +.errors { + background-color: #eeeeee; text-align: left; font-style: italic; opacity: 50%; + /*border-top: 28px; /*border-right: 14px;*/ + margin-left: 25%; margin-right: 25%; padding-top: 5px; padding-bottom: 5px; + -webkit-border-bottom-left-radius: 14px 14px; + -webkit-border-bottom-right-radius: 14px 14px; + -webkit-border-top-left-radius: 14px 14px; + -webkit-border-top-right-radius: 14px 14px; +} +.hideerrors .errorlist { display: none; } .example { background-color: #ffffcc; margin: 50px 0; padding: 10px 0; } .boxbottom div { background: url(bl.png) no-repeat bottom left; } .boxbottom { background: url(br.png) no-repeat bottom right; } This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |