svv/orders.py
changeset 9 0327b83959e9
parent 7 bbac4b0f4320
child 10 4bdb45071c89
equal deleted inserted replaced
8:27e37082625e 9:0327b83959e9
     6 from svv.controllers import PageHandler
     6 from svv.controllers import PageHandler
     7 from svv.html import tags
     7 from svv.html import tags
     8 from svv import database as db
     8 from svv import database as db
     9 
     9 
    10 import datetime
    10 import datetime
    11 
    11 import logging
    12 class OrdersView (PageHandler) :
    12 
    13     def render (self) :
    13 log = logging.getLogger('svv.orders')
    14         return tags.h1("Orders list")
    14 
    15 
    15 class FormError (Exception) :
    16 class OrderView (PageHandler) :
    16     """
    17     def render (self) :
    17         A user-level error in a form field
    18         return tags.h1("Order info")
    18     """
    19 
    19 
    20 class NewOrderView (PageHandler) :
    20     def __init__ (self, field, error) :
    21     def render_form_field (self, title, description, name, input) :
    21         """
    22         return tags.li(class_='field')(
    22                 field       - name of field with error
    23             tags.label(title, for_=name),
    23                 error       - descriptive text for user
    24 
    24         """
    25             input,
    25 
    26 
    26         self.field = field
    27             # tags.span("Error!"),
    27 
    28 
    28         super(FormError, self).__init__(error)
    29             tags.p(description),
    29 
    30         )
    30 class OrderForm (object) :
    31 
    31     """
    32     def get_customer_list (self) :
    32         A single instance of a <form>, where we can process submitted data from the client, storing the associated
    33         """
    33         Order-related data, and then render a form for any of that related data.
    34             Get (id, name) list of customers
    34     """
    35         """
    35     
    36 
    36     # any POST data we have processed, updated from process()
    37         return self.app.query(db.select([db.customers.c.id, db.customers.c.name]))
    37     data = None
    38 
    38 
    39     def get_contact_list (self, customer_id=None) :
    39     def __init__ (self, app) :
    40         """
    40         """
    41             Get (id, name, phone, email) list of contacts, optionally for given customer if given.
    41                 app             - bind this form to the app state (db etc)
    42         """
    42         """
    43 
    43 
    44         query = db.select([db.contacts.c.id, db.contacts.c.name, db.contacts.c.phone, db.contacts.c.email])
    44         self.app = app
       
    45 
       
    46     def defaults (self) :
       
    47         """
       
    48             Update our attributes with default values
       
    49         """
       
    50 
       
    51         self.customer_id = None
       
    52         self.customer_name = None
       
    53 
       
    54         self.contact_id = None
       
    55         self.contact_name = None
       
    56         self.contact_phone = None
       
    57         self.contact_email = None
       
    58         self.contact_customer = None
       
    59 
       
    60         self.event_name = None
       
    61         self.event_description = None
       
    62 
       
    63         tomorrow = datetime.date.today() + datetime.timedelta(days=1)
       
    64         
       
    65         # default to tomorrow afternoon
       
    66         self.event_start = datetime.datetime.combine(tomorrow, datetime.time(16, 00))
       
    67         
       
    68         # automatically determined once start is set
       
    69         self.event_end = None
       
    70 
       
    71     def process_raw_field (self, name, default=None, required=None) :
       
    72         """
       
    73             Process a generic incoming data field.
       
    74 
       
    75                 default         - value to return if no value was present
       
    76                 required        - raise a FormError if no value present
       
    77 
       
    78             Returns the value as a str, or default
       
    79         """
       
    80 
       
    81         if name in self.post :
       
    82             return self.post[name]
       
    83 
       
    84         elif required :
       
    85             raise FormError(name, "Required field")
       
    86 
       
    87         else :
       
    88             return default
       
    89 
       
    90     def process_string_field (self, name, default=None, required=None, strip=True) :
       
    91         """
       
    92             Process a generic incoming string field.
       
    93 
       
    94             Trims extra whitespace from around the value, unless strip=False is given.
       
    95 
       
    96             Returns the value as unicode, or default.
       
    97         """
       
    98 
       
    99         value = self.process_raw_field(name, required=required)
       
   100 
       
   101         if value is None :
       
   102             return default
       
   103 
       
   104         try :
       
   105             # XXX: decode somehow, or can werkzeug handle that?
       
   106             value = unicode(value)
       
   107 
       
   108         except UnicodeDecodeError :
       
   109             raise FormError(name, "Failed to decode Unicode characters")
       
   110 
       
   111         if strip :
       
   112             value = value.strip()
       
   113 
       
   114         return value
       
   115     
       
   116     def process_integer_field (self, name, default=None, required=None) :
       
   117         """
       
   118             Process a generic incoming int field.
       
   119 
       
   120             Returns the value as int, or default.
       
   121         """
       
   122 
       
   123         value = self.process_raw_field(name, required=required)
       
   124 
       
   125         if value is None :
       
   126             return default
       
   127 
       
   128         try :
       
   129             return int(value)
       
   130 
       
   131         except ValueError :
       
   132             raise FormError(name, "Must be a number")
       
   133     
       
   134     DATETIME_FORMAT = "%d.%m.%Y %H:%M"
       
   135 
       
   136     def process_datetime_field (self, name, default=None, required=None, format=DATETIME_FORMAT) :
       
   137         """
       
   138             Process an incoming datetime field.
       
   139 
       
   140             Returns the value as datetime, or default.
       
   141         """
       
   142 
       
   143         value = self.process_raw_field(name, required=required)
       
   144 
       
   145         if value is None :
       
   146             return default
       
   147 
       
   148         try :
       
   149             return datetime.datetime.strptime(value, format)
       
   150 
       
   151         except ValueError, ex :
       
   152             raise FormError(name, "Invalid date/time value: " + str(ex))
       
   153 
       
   154     def process_multifield (self, table, id, fields) :
       
   155         """
       
   156             Process a set of user-given field values for an object with an unique id, and some set of additional fields.
       
   157             
       
   158             If the id is given, look up the corresponding field values, and return those.
       
   159 
       
   160             If any of the fields are given, either look up a matching row, or create a new one, and return its id.
       
   161 
       
   162             Returns an (id_name, field_name, ...) N-tuple.
       
   163         """
       
   164 
       
   165         id_name, id_col, id_value = id
       
   166 
       
   167         if id_value :
       
   168             # look up object from db
       
   169             columns = [col for name, col, value in fields]
       
   170 
       
   171             sql = db.select(columns, (id_col == id_value))
       
   172 
       
   173             for row in sql :
       
   174                 # XXX: sanity-check row values vs our values
       
   175 
       
   176                 # new values
       
   177                 fields = tuple(
       
   178                     (name, col, row[col]) for name, col, value in fields
       
   179                 )
       
   180                 
       
   181                 # ok, just use the first one
       
   182                 break
       
   183 
       
   184             else :
       
   185                 # not found!
       
   186                 raise FormError(id_name, "Item selected does not seem to exist")
       
   187                 
       
   188             log.info("Lookup %s=%d -> %s", id_name, id_value, dict((name, value) for name, col, value in fields))
       
   189 
       
   190         elif any(value for name, col, value in fields) :
       
   191             # look up identical object from db?
       
   192             sql = db.select([id_col], db.and_(*[(col == value) for name, col, value in fields]))
       
   193 
       
   194             for row in self.app.query(sql) :
       
   195                 if id_value :
       
   196                     log.warn("Duplicate %s=%d for %s", id_name, id_value, dict((name, value) for name, col, value in fields))
       
   197                 
       
   198                 # found object's id
       
   199                 id_value = row[id_col]
       
   200 
       
   201                 log.info("Found %s -> %d", dict((name, value) for name, col, value in fields), id_value)
       
   202             
       
   203             # create new object?
       
   204             if not id_value :
       
   205                 sql = db.insert(table).values(dict((col, value) for name, col, value in fields))
       
   206 
       
   207                 id_value, = self.app.insert(sql)
       
   208 
       
   209                 log.info("Create %s -> %d", dict((name, value) for name, col, value in fields), id_value)
       
   210 
       
   211         else :
       
   212             # not known
       
   213             log.debug("No %s known for order...", id_name)
       
   214         
       
   215         # return full set of values
       
   216         return (id_value, ) + tuple(value for name, col, value in fields)
       
   217 
       
   218     def process_customer (self) :
       
   219         """
       
   220             Process the incoming customer_* fields, returning (customer_id, customer_name).
       
   221         """
       
   222 
       
   223         return self.process_multifield(db.customers,
       
   224             ('customer_id', db.customers.c.id, self.process_integer_field('customer_id')),
       
   225             (
       
   226                 ('customer_name', db.customers.c.name, self.process_string_field('customer_name')),
       
   227             ),
       
   228         )
       
   229 
       
   230     def process_contact (self, customer_id) :
       
   231         """
       
   232             Process the incoming contact_* fields, returning 
       
   233                 (contact_id, contact_name, contact_phone, contact_email, contact_customer)
       
   234         """
       
   235 
       
   236         return self.process_multifield(db.contacts,
       
   237             ('contact_id', db.contacts.c.id, self.process_integer_field('contact_id')),
       
   238             (
       
   239                 ('contact_name', db.contacts.c.name, self.process_string_field('contact_name')),
       
   240                 ('contact_phone', db.contacts.c.phone, self.process_string_field('contact_phone')),
       
   241                 ('contact_email', db.contacts.c.email, self.process_string_field('contact_email')),
       
   242                 ('contact_customer', db.contacts.c.customer, customer_id),
       
   243             ),
       
   244         )
       
   245 
       
   246     def process_event (self) :
       
   247         """
       
   248             Process the incoming event_* fields, returning
       
   249                 (event_name, event_description, event_start, event_end)
       
   250         """
       
   251 
       
   252         event_name = self.process_string_field('event_name')
       
   253         event_description = self.process_string_field('event_description', strip=False)
       
   254         event_start = self.process_datetime_field('event_start')
       
   255         event_end = self.process_datetime_field('event_end')
       
   256 
       
   257         return (event_name, event_description, event_start, event_end)
       
   258 
       
   259     def process (self, data) :
       
   260         """
       
   261             Bind ourselves to the given incoming POST data, and update our order field attributes.
       
   262 
       
   263                 data        - the submitted POST data as a MultiDict
       
   264         """
       
   265 
       
   266         # bind the raw post data
       
   267         self.data = data
       
   268 
       
   269         # customer
       
   270         self.customer_id, self.customer_name = self.process_customer()
       
   271 
       
   272         # contact
       
   273         self.contact_id, self.contact_name, self.contact_phone, self.contact_email, self.contact_customer = self.process_contact(customer_id)
       
   274 
       
   275         if self.contact_customer and not self.customer_id :
       
   276             # TODO: be really smart?
       
   277             pass
       
   278 
       
   279         # event
       
   280         self.event_name, self.event_description, self.event_start, self.event_end = self.process_event()
       
   281 
       
   282 
       
   283     def build_customer_list (self) :
       
   284         """
       
   285             Query a (id, name) list of customers.
       
   286         """
       
   287 
       
   288         sql = db.select([db.customers.c.id, db.customers.c.name])
       
   289 
       
   290         return self.app.query(sql)
       
   291 
       
   292     def build_contact_list (self, customer_id=None) :
       
   293         """
       
   294             Query a (id, name, phone, email) list of contacts, optionally for given customer if given.
       
   295         """
       
   296 
       
   297         sql = db.select([db.contacts.c.id, db.contacts.c.name, db.contacts.c.phone, db.contacts.c.email])
    45 
   298 
    46         if customer_id :
   299         if customer_id :
    47             query = query.where((db.contacts.c.customer == customer_id))
   300             sql = sql.where((db.contacts.c.customer == customer_id))
    48 
   301 
    49         return self.app.query(query)
   302         return self.app.query(sql)
       
   303 
       
   304 
       
   305     def render_text_input (self, name, value=None, multiline=False) :
       
   306         """
       
   307             Render HTML for a generic text field input.
       
   308                 
       
   309                 name            - field name, as used for POST
       
   310                 value           - default field value
       
   311                 multiline       - use a multi-line <textarea> instead
       
   312         """
       
   313 
       
   314         if multiline :
       
   315             # XXX: textarea can't be self-closing for some reason?
       
   316             return tags.textarea(name=name, id=name, _selfclosing=False, _whitespace_sensitive=True)(value)
       
   317 
       
   318         else :
       
   319             return tags.input(type='text', name=name, id=name, value=value)
       
   320 
       
   321     def render_select_input (self, name, options, value=None) :
       
   322         """
       
   323             Render HTML for a generic select control.
       
   324 
       
   325                 name            - field name, as used for POST
       
   326                 options         - sequence of (value, title) options. `value` may be None to omit.
       
   327                 value           - the selected value
       
   328         """
       
   329 
       
   330         return tags.select(name=name, id=name)(
       
   331             (
       
   332                 tags.option(value=opt_value, selected=('selected' if opt_value == value else None))(opt_title)
       
   333             ) for opt_value, opt_title in options
       
   334         )
    50 
   335 
    51     def render_customer_input (self) :
   336     def render_customer_input (self) :
    52         """
   337         """
    53             Render HTML for customer field <input>s
   338             Render HTML for customer_id/name field inputs.
    54         """
   339         """
    55 
   340 
    56         # pre-selected values?
   341         # all known customers
    57         customer_id = self.POST.get('customer_id')
   342         customers = self.build_customer_list()
    58         customer_name = self.POST.get('customer_name')
       
    59 
       
    60         # available values
       
    61         customers = self.get_customer_list()
       
    62         
   343         
    63         return (
   344         return (
    64             tags.select(name='customer_id', id='customer_id')(
   345             self.render_select_input('customer_id', customers, self.customer_id),
    65                 tags.option(value=0)(u"Luo uusi"),
   346             self.render_text_input('customer_name', self.customer_name),
    66 
       
    67                 [(
       
    68                     tags.option(value=id, selected=('selected' if id == customer_id or name == customer_name else None))(name)
       
    69                 ) for id, name in customers],
       
    70             ),
       
    71             tags.input(type='text', name='customer_name', id='customer_name'),
       
    72 
   347 
    73             tags.script(r"$(document).ready(function () { $('#customer_id').formSelectPreset({textTarget: $('#customer_name')}); });"),
   348             tags.script(r"$(document).ready(function () { $('#customer_id').formSelectPreset({textTarget: $('#customer_name')}); });"),
    74         )
   349         )
    75 
   350 
    76     def render_contact_input (self) :
   351     def render_contact_input (self) :
    77         """
   352         """
    78             Render HTML for contact name field <input>s
   353             Render HTML for contact name field <input>s
    79         """
   354         """
    80 
   355         # recommended contacts for selected customer, if known
    81         # pre-selected values
   356         contacts = self.get_contact_list(self.customer_id)
    82         customer_id = self.POST.get('customer_id')
       
    83         contact_id = self.POST.get('contact_id')
       
    84         contact_name = self.POST.get('contact_name')
       
    85         contact_phone = self.POST.get('contact_phone')
       
    86         contact_email = self.POST.get('contact_email')
       
    87 
       
    88         # available values
       
    89         contacts = self.get_contact_list()
       
    90 
   357 
    91         return (
   358         return (
    92             tags.select(name='contact_id', id='contact_id')(
   359             self.render_select_input('contact_id', ((id, name) for id, name, phone, email in contacts), self.contact_id)
    93                 tags.option(value=0)(u"Luo uusi"),
   360             self.render_text_input('contact_name', self.contact_name),
    94 
       
    95                 [(
       
    96                     tags.option(value=id, selected=('selected' if id == contact_id else None))(name)
       
    97                 ) for id, name, phone, email in contacts],
       
    98             ),
       
    99 
       
   100             tags.input(type='text', name='contact_name', id='contact_name'),
       
   101 
   361 
   102             tags.script(r"$(document).ready(function () { $('#contact_id').formSelectPreset({textTarget: $('#contact_name')}); });"),
   362             tags.script(r"$(document).ready(function () { $('#contact_id').formSelectPreset({textTarget: $('#contact_name')}); });"),
   103         )
   363         )
   104 
   364 
   105     DATETIME_FORMAT = "%d.%m.%Y %H:%M"
       
   106 
       
   107     def get_POST_datetime (self, name, default=None) :
       
   108         """
       
   109             Return a datetime for something the client POST'd
       
   110         """
       
   111 
       
   112         value = self.POST.get(name)
       
   113 
       
   114         if value :
       
   115             # XXX: handle invalid format..
       
   116             return datetime.datetime.strptime(value, self.DATETIME_FORMAT)
       
   117 
       
   118         else :
       
   119             return default
       
   120 
       
   121     def render_event_input (self) :
   365     def render_event_input (self) :
   122         """
   366         """
   123             Render HTML for event start/end field <input>s
   367             Render HTML for event start/end field <input>s
   124         """
   368         """
   125         
   369         
   126         # XXX: sensible defaults?
       
   127         tomorrow = datetime.date.today() + datetime.timedelta(days=1)
       
   128         default_start = datetime.datetime.combine(tomorrow, datetime.time(16, 00))
       
   129         
       
   130         # automatically determined once start is set
       
   131         default_end = None
       
   132 
       
   133         # pre-selected values
       
   134         event_start = self.get_POST_datetime('event_start', default_start)
       
   135         event_end = self.get_POST_datetime('event_end', default_end)
       
   136 
       
   137         return (
   370         return (
   138             tags.input(type='text', name='event_start', id='event_start', value=(event_start.strftime(self.DATETIME_FORMAT) if event_start else None)),
   371             self.render_text_input('event_start', (self.event_start.strftime(self.DATETIME_FORMAT) if event_start else None)),
   139             " - ",
   372             " - ",
   140             tags.input(type='text', name='event_end', id='event_end', value=(event_end.strftime(self.DATETIME_FORMAT) if event_end else None)),
   373             self.render_text_input('event_end', (self.event_end.strftime(self.DATETIME_FORMAT) if event_end else None)),
   141 
   374 
   142             tags.script(r"""
   375             tags.script(r"""
   143 $(document).ready(function () { 
   376 $(document).ready(function () { 
   144     var event_start = $('#event_start');
   377     var event_start = $('#event_start');
   145     var event_end = $('#event_end');
   378     var event_end = $('#event_end');
   146 
   379 
   147     event_start.datetimepicker();
   380     event_start.datetimepicker();
   148     event_end.datetimepicker(); 
   381     event_end.datetimepicker(); 
   149     
   382     
       
   383 /* Buggy shit doesn't work
       
   384 
       
   385     {
       
   386         beforeShow: function (input, inst) {
       
   387             // copy default value from event_start
       
   388             event_end.datetimepicker("option", "defaultDate", event_start.datetimepicker("getDate"));
       
   389         }
       
   390     }   
       
   391 
   150     event_start.change(function () {
   392     event_start.change(function () {
   151         // copy value as default
   393         // copy value as default
   152         var start_date = event_start.datetimepicker("getDate");
   394         var start_date = event_start.datetimepicker("getDate");
   153 
   395 
   154         event_end.datetimepicker("option", "defaultDate", start_date);
   396         event_end.datetimepicker("option", "defaultDate", start_date);
   155     });
   397     });
   156 
   398 
   157     // init default as well
   399     // init default as well
   158     event_start.change();
   400     event_start.change();
       
   401 */
   159 });"""      ),
   402 });"""      ),
   160 
   403 
   161         )
   404         )
   162 
   405 
   163     def render (self) :
   406     def render_form_field (self, name, title, description, inputs) :
   164         return tags.form(action=self.build_url(NewOrderView), method='POST')(
   407         """
   165             tags.h1(u"Uusi tilaus"),
   408             Render the label, input control, error note and description for a single field, along with their containing <li>.
   166 
   409         """
       
   410 
       
   411         return tags.li(class_='field')(
       
   412             tags.label(title, for_=name),
       
   413 
       
   414             inputs,
       
   415 
       
   416             # XXX: somewhere where we tag these!
       
   417             # tags.span("Error!"),
       
   418 
       
   419             tags.p(description),
       
   420         )
       
   421 
       
   422 
       
   423     def render (self, action, submit=u"Tallenna") :
       
   424         """
       
   425             Render the entire <form>, using any loaded/processed values.
       
   426 
       
   427                 action          - the target URL for the form to POST to
       
   428                 submit          - label for the submit button
       
   429         """
       
   430 
       
   431         return tags.form(action=action, method='POST')(
   167             tags.fieldset(
   432             tags.fieldset(
   168                 tags.legend(u"Tilaaja"),
   433                 tags.legend(u"Tilaaja"),
   169                 
   434                 
   170                 tags.ol(
   435                 tags.ol(
   171                     self.render_form_field(u"Tilaaja", u"Tilaavan yhdistyksen/henkilön nimi", 'customer_name', self.render_customer_input()),
   436                     self.render_form_field('customer_name', u"Tilaaja", u"Tilaavan yhdistyksen/henkilön nimi", (
   172 
   437                         self.render_customer_input()
   173                     self.render_form_field(u"Yhteyshenkilö", u"Yhteyshenkilön nimi, jos eri kun tilaaja", 'contact_name', self.render_contact_input()),
   438                     )),
   174 
   439 
   175                     self.render_form_field(u"Puhelin", u"Yhteyshenkilön puhelinnumero", 'contact_phone', (
   440                     self.render_form_field('contact_name', u"Yhteyshenkilö", u"Yhteyshenkilön nimi, jos eri kun tilaaja", (
   176                         tags.input(type='text', name='contact_phone')
   441                         self.render_contact_input()
   177                     )),
   442                     )),
   178 
   443 
   179                     self.render_form_field(u"Sähköposti", u"Yhteyshenkilön sähköpostiosoite", 'contact_email', (
   444                     self.render_form_field('contact_phone', u"Puhelin", u"Yhteyshenkilön puhelinnumero", (
   180                         tags.input(type='text')
   445                         self.render_text_input('contact_phone', self.contact_phone)
       
   446                     )),
       
   447 
       
   448                     self.render_form_field('contact_email', u"Sähköposti", u"Yhteyshenkilön sähköpostiosoite", (
       
   449                         self.render_text_input('contact_email', self.contact_email)
   181                     )),
   450                     )),
   182                 ),
   451                 ),
   183             ),
   452             ),
   184 
   453 
   185             tags.fieldset(
   454             tags.fieldset(
   186                 tags.legend(u"Tapahtuma"),
   455                 tags.legend(u"Tapahtuma"),
   187            
   456            
   188                 tags.ol(
   457                 tags.ol(
   189                     self.render_form_field(u"Tapahtuma", u"Tapahtuman lyhyt nimi", 'event_name', (
   458                     self.render_form_field('event_name', u"Tapahtuma", u"Tapahtuman lyhyt nimi", (
   190                         tags.input(type='text', name='event_name')
   459                         self.render_text_input('event_name', self.event_name)
   191                     )),
   460                     )),
   192 
   461 
   193                     self.render_form_field(u"Lisätiedot", u"Tapahtuman tarkemmat tiedot", 'event_description', (
   462                     self.render_form_field('event_description', u"Lisätiedot", u"Tapahtuman tarkemmat tiedot", (
   194                         tags.textarea("", rows=8, name='event_description')
   463                         self.render_text_input('event_description', self.event_description, multiline=True)
   195                     )),
   464                     )),
   196 
   465 
   197                     self.render_form_field(u"Ajankohta", u"Tapahtuman ajankohta (kamat noudetaan - palautetaan)", 'event_start', self.render_event_input()),
   466                     self.render_form_field('event_start', u"Ajankohta", u"Tapahtuman ajankohta (kamat noudetaan - palautetaan)", (
       
   467                         self.render_event_input()
       
   468                     )),
   198                 ),
   469                 ),
   199             ),
   470             ),
   200 
   471 
   201             tags.input(type='submit', value="Tallenna"),
   472             tags.input(type='submit', value=submit),
   202         )
   473         )
   203 
   474 
       
   475 
       
   476 class OrdersView (PageHandler) :
       
   477     def render (self) :
       
   478         return tags.h1("Orders list")
       
   479 
       
   480 class OrderView (PageHandler) :
       
   481     def render (self, id) :
       
   482         return tags.h1("Order info for #%d" % (id, ))
       
   483 
       
   484 class NewOrderView (PageHandler) :
       
   485     """
       
   486         
       
   487     """
       
   488 
       
   489     def render (self) :
       
   490         if self.POST :
       
   491             print self.POST
       
   492 
       
   493             # 
       
   494             
       
   495             # if we've gotten this far, then we can create it!
       
   496             sql = db.insert(db.orders).values(
       
   497                 customer            = customer_id,
       
   498                 contact             = contact_id,
       
   499                 
       
   500                 event_name          = event_name,
       
   501                 event_description   = event_description,
       
   502                 event_start         = event_start,
       
   503                 event_end           = event_end,
       
   504             )
       
   505 
       
   506             # go!
       
   507             order_id, = self.app.insert(sql)
       
   508 
       
   509             # ok, we don't need the /new URL anymore, we can just show the order page
       
   510             return self.redirect_for(OrderView, id=order_id)
       
   511 
       
   512         # render form
       
   513         return self.render_form()
       
   514 
       
   515