--- a/svv/orders.py Thu Dec 23 01:07:42 2010 +0200
+++ b/svv/orders.py Thu Dec 23 02:22:34 2010 +0200
@@ -9,6 +9,7 @@
import datetime
import logging
+import collections
log = logging.getLogger('svv.orders')
@@ -17,13 +18,16 @@
A user-level error in a form field
"""
- def __init__ (self, field, error) :
+ def __init__ (self, field, value, error) :
"""
field - name of field with error
+ value - the errenous value in the form that we recieved it
+ may be None if it was the *lack* of a value that caused the issue
error - descriptive text for user
"""
self.field = field
+ self.value = value
super(FormError, self).__init__(error)
@@ -43,6 +47,9 @@
self.app = app
+ # accumulated errors
+ self.errors = collections.defaultdict(list)
+
def defaults (self) :
"""
Update our attributes with default values
@@ -78,11 +85,11 @@
Returns the value as a str, or default
"""
- if name in self.post :
- return self.post[name]
+ if name in self.data :
+ return self.data[name]
elif required :
- raise FormError(name, "Required field")
+ raise FormError(name, None, "Required field")
else :
return default
@@ -106,7 +113,7 @@
value = unicode(value)
except UnicodeDecodeError :
- raise FormError(name, "Failed to decode Unicode characters")
+ raise FormError(name, value, "Failed to decode Unicode characters")
if strip :
value = value.strip()
@@ -129,7 +136,7 @@
return int(value)
except ValueError :
- raise FormError(name, "Must be a number")
+ raise FormError(name, value, "Must be a number")
DATETIME_FORMAT = "%d.%m.%Y %H:%M"
@@ -149,7 +156,7 @@
return datetime.datetime.strptime(value, format)
except ValueError, ex :
- raise FormError(name, "Invalid date/time value: " + str(ex))
+ raise FormError(name, value, "Invalid date/time value: " + str(ex))
def process_multifield (self, table, id, fields) :
"""
@@ -170,7 +177,7 @@
sql = db.select(columns, (id_col == id_value))
- for row in sql :
+ for row in self.app.query(sql) :
# XXX: sanity-check row values vs our values
# new values
@@ -183,7 +190,7 @@
else :
# not found!
- raise FormError(id_name, "Item selected does not seem to exist")
+ raise FormError(id_name, id_value, "Item selected does not seem to exist")
log.info("Lookup %s=%d -> %s", id_name, id_value, dict((name, value) for name, col, value in fields))
@@ -220,47 +227,88 @@
Process the incoming customer_* fields, returning (customer_id, customer_name).
"""
- return self.process_multifield(db.customers,
- ('customer_id', db.customers.c.id, self.process_integer_field('customer_id')),
- (
- ('customer_name', db.customers.c.name, self.process_string_field('customer_name')),
- ),
- )
+ try :
+ customer_id = self.process_integer_field('customer_id')
+ customer_name = self.process_string_field('customer_name')
+ if not customer_id and not customer_name :
+ raise FormError('customer_name', None, "Must enter a customer")
+
+ return self.process_multifield(db.customers,
+ ('customer_id', db.customers.c.id, customer_id),
+ (
+ ('customer_name', db.customers.c.name, customer_name),
+ ),
+ )
+
+ except FormError, e :
+ # list it
+ self.fail_field(e, 'customer_name')
+
+ return None, None
+
def process_contact (self, customer_id) :
"""
Process the incoming contact_* fields, returning
(contact_id, contact_name, contact_phone, contact_email, contact_customer)
"""
+
+ try :
+ contact_id = self.process_integer_field('contact_id')
+ contact_name = self.process_string_field('contact_name')
+ contact_phone = self.process_string_field('contact_phone')
+ contact_email = self.process_string_field('contact_email')
+ contact_customer = customer_id
- return self.process_multifield(db.contacts,
- ('contact_id', db.contacts.c.id, self.process_integer_field('contact_id')),
- (
- ('contact_name', db.contacts.c.name, self.process_string_field('contact_name')),
- ('contact_phone', db.contacts.c.phone, self.process_string_field('contact_phone')),
- ('contact_email', db.contacts.c.email, self.process_string_field('contact_email')),
- ('contact_customer', db.contacts.c.customer, customer_id),
- ),
- )
+ if not contact_id and not (contact_name or contact_phone or contact_email) :
+ raise FormError('contact_name', None, "Must enter a contact")
+
+ return self.process_multifield(db.contacts,
+ ('contact_id', db.contacts.c.id, contact_id),
+ (
+ ('contact_name', db.contacts.c.name, contact_name),
+ ('contact_phone', db.contacts.c.phone, contact_phone),
+ ('contact_email', db.contacts.c.email, contact_email),
+ ('contact_customer', db.contacts.c.customer, contact_customer),
+ ),
+ )
+
+ except FormError, e :
+ # list it
+ self.fail_field(e, 'contact_name' if e.field == 'contact_id' else None)
+
+ return None, None, None, None, None
def process_event (self) :
"""
Process the incoming event_* fields, returning
(event_name, event_description, event_start, event_end)
"""
+
+ try :
+ event_name = self.process_string_field('event_name')
+ event_description = self.process_string_field('event_description', strip=False)
+ event_start = self.process_datetime_field('event_start')
+ event_end = self.process_datetime_field('event_end')
- event_name = self.process_string_field('event_name')
- event_description = self.process_string_field('event_description', strip=False)
- event_start = self.process_datetime_field('event_start')
- event_end = self.process_datetime_field('event_end')
+ if event_end < event_start :
+ raise FormError('event_start', event_end, "Event must end after start")
- return (event_name, event_description, event_start, event_end)
+ return (event_name, event_description, event_start, event_end)
+
+ except FormError, e :
+ # list it
+ self.fail_field(e)
+
+ return None, None, None, None
def process (self, data) :
"""
Bind ourselves to the given incoming POST data, and update our order field attributes.
data - the submitted POST data as a MultiDict
+
+ Returns True if all fields were processed without errors, False otherwise.
"""
# bind the raw post data
@@ -270,7 +318,7 @@
self.customer_id, self.customer_name = self.process_customer()
# contact
- self.contact_id, self.contact_name, self.contact_phone, self.contact_email, self.contact_customer = self.process_contact(customer_id)
+ self.contact_id, self.contact_name, self.contact_phone, self.contact_email, self.contact_customer = self.process_contact(self.customer_id)
if self.contact_customer and not self.customer_id :
# TODO: be really smart?
@@ -278,7 +326,22 @@
# event
self.event_name, self.event_description, self.event_start, self.event_end = self.process_event()
+
+ return not self.errors
+ def fail_field (self, form_error, field=None) :
+ """
+ Mark the field mentioned inside the given FormError as failed.
+
+ form_error - the FormError to store
+ field - the name of the field to store the error under, if not the same as in form_error
+ """
+
+ field = field or form_error.field
+
+ log.warn("Marking field %s as failed: %s", field, form_error)
+
+ self.errors[field].append(form_error)
def build_customer_list (self) :
"""
@@ -353,10 +416,10 @@
"""
# all known customers
- customers = self.build_customer_list()
+ customers = list(self.build_customer_list())
return (
- self.render_select_input('customer_id', customers, self.customer_id),
+ self.render_select_input('customer_id', [(0, u"Luo uusi")] + customers, self.customer_id),
self.render_text_input('customer_name', self.customer_name),
tags.script(r"$(document).ready(function () { $('#customer_id').formSelectPreset({textTarget: $('#customer_name')}); });"),
@@ -370,7 +433,7 @@
contacts = self.build_contact_list(self.customer_id)
return (
- self.render_select_input('contact_id', ((id, name) for id, name, phone, email in contacts), self.contact_id),
+ self.render_select_input('contact_id', [(0, u"Luo uusi")] + [(id, name) for id, name, phone, email in contacts], self.contact_id),
self.render_text_input('contact_name', self.contact_name),
tags.script(r"$(document).ready(function () { $('#contact_id').formSelectPreset({textTarget: $('#contact_name')}); });"),
@@ -419,15 +482,21 @@
Render the label, input control, error note and description for a single field, along with their containing <li>.
"""
- return tags.li(class_='field')(
- tags.label(title, for_=name),
+ # any errors for this field?
+ errors = self.errors[name]
+
+ return tags.li(class_='field' + (' failed' if errors else ''))(
+ tags.label((
+ title,
+ tags.strong(u"(Virheellinen)") if errors else None,
+ ), for_=name),
inputs,
- # XXX: somewhere where we tag these!
- # tags.span("Error!"),
-
tags.p(description),
+
+ # possible errors
+ tags.ul(class_='errors')(tags.li(error.message) for error in errors) if errors else None,
)
@@ -440,6 +509,18 @@
"""
return tags.form(action=action, method='POST')(
+ (
+ tags.h3(u"Lomakkeessa oli virheitä"),
+ tags.p(u"Korjaa lomake ja lähetä uudelleen"),
+
+ tags.ul(class_='errors')(
+ tags.li(tags.a(href='#' + error.field)(
+ tags.em(error.field),
+ error.message,
+ )) for field_errors in self.errors.itervalues() for error in field_errors
+ ),
+ ) if self.errors else None,
+
tags.fieldset(
tags.legend(u"Tilaaja"),
@@ -494,38 +575,63 @@
class NewOrderView (PageHandler) :
"""
-
+ Render form for input, let the user correct their errors, create the order, and redirect out.
"""
- def render_content (self) :
-
- form = OrderForm(self.app)
- form.defaults()
+ def create (self, form) :
+ """
+ Create the new order from the given form data, returning the new order's ID
+ """
- return form.render(action=self.url_for(NewOrderView))
-
- # XXX: under construction..
-
- if self.POST :
- print self.POST
-
+ # if we've gotten this far, then we can create it!
+ sql = db.insert(db.orders).values(
+ customer = form.customer_id,
+ contact = form.contact_id,
- # if we've gotten this far, then we can create it!
- sql = db.insert(db.orders).values(
- customer = customer_id,
- contact = contact_id,
+ event_name = form.event_name,
+ event_description = form.event_description,
+ event_start = form.event_start,
+ event_end = form.event_end,
+ )
+
+ # go!
+ order_id, = self.app.insert(sql)
+
+ # great
+ return order_id
+
+ def process (self) :
+ """
+ Set up up our form.
+ """
+
+ self.form = OrderForm(self.app)
+
+ # use either POST data or defaults
+ if self.POST :
+ # try and process the input, checking for any failures...
+ if self.form.process(self.POST) :
+ # should be good, create it!
+ order_id = self.create(self.form)
- event_name = event_name,
- event_description = event_description,
- event_start = event_start,
- event_end = event_end,
- )
+ # redirect there now that our business is done and the order exists
+ return self.redirect_for(OrderView, id=order_id)
+
+ else :
+ # errors in form input
+ pass
- # go!
- order_id, = self.app.insert(sql)
+ else :
+ # init from defaults
+ self.form.defaults()
+
+ def render_content (self) :
+ """
+ Render our form
+ """
- # ok, we don't need the /new URL anymore, we can just show the order page
- return self.redirect_for(OrderView, id=order_id)
+ return (
+ tags.h1(u"Uusi tilaus"),
+ self.form.render(action=self.url_for(NewOrderView))
+ )
-
-