diff --git a/tailbone/templates/master/edit.mako b/tailbone/templates/master/edit.mako
index 79bc533f..9599a2c2 100644
--- a/tailbone/templates/master/edit.mako
+++ b/tailbone/templates/master/edit.mako
@@ -3,6 +3,23 @@
 
 <%def name="title()">Edit ${model_title}: ${instance_title}%def>
 
+<%def name="head_tags()">
+  ${parent.head_tags()}
+  
+%def>
+
 <%def name="context_menu_items()">
   
${h.link_to("Back to {}".format(model_title_plural), url(route_prefix))} 
   % if master.viewable and request.has_perm('{}.view'.format(permission_prefix)):
diff --git a/tailbone/templates/master/view_row.mako b/tailbone/templates/master/view_row.mako
index dc5fd532..b03ae6e0 100644
--- a/tailbone/templates/master/view_row.mako
+++ b/tailbone/templates/master/view_row.mako
@@ -11,7 +11,7 @@
   % if master.rows_deletable and instance_deletable and request.has_perm('{}.delete'.format(permission_prefix)):
       ${h.link_to("Delete this {}".format(model_title), action_url('delete', instance))} 
   % endif
-  % if master.rows_creatable and request.has_perm('{}.create'.format(permission_prefix)):
+  % if rows_creatable and request.has_perm('{}.create'.format(permission_prefix)):
       ${h.link_to("Create a new {}".format(model_title), url('{}.create'.format(route_prefix)))} 
   % endif
 %def>
diff --git a/tailbone/templates/newbatch/edit.mako b/tailbone/templates/newbatch/edit.mako
index 91ea1dc9..120ec8fa 100644
--- a/tailbone/templates/newbatch/edit.mako
+++ b/tailbone/templates/newbatch/edit.mako
@@ -50,10 +50,16 @@
 
 
 
-  ${form.render(buttons=capture(buttons))|n}
-
+  % if master.edit_with_rows:
+      ${form.render(buttons=capture(buttons))|n}
+  % else:
+      ${form.render()|n}
+  % endif
+
 
-${rows_grid.render_complete(allow_save_defaults=False, tools=capture(self.grid_tools))|n}
+% if master.edit_with_rows:
+    ${rows_grid.render_complete(allow_save_defaults=False, tools=capture(self.grid_tools))|n}
+% endif
 
 
 
diff --git a/tailbone/templates/purchases/batches/view.mako b/tailbone/templates/purchases/batches/view.mako
index f877163d..9a97debd 100644
--- a/tailbone/templates/purchases/batches/view.mako
+++ b/tailbone/templates/purchases/batches/view.mako
@@ -5,6 +5,7 @@
   ${parent.head_tags()}
   
 %def>
 
 <%def name="leading_buttons()">
-  % if not instance.executed and request.has_perm('purchases.batch.order_form'):
+  % if not batch.complete and not batch.executed and request.has_perm('purchases.batch.order_form'):
       View as Order Form 
   % endif
 %def>
diff --git a/tailbone/views/batch.py b/tailbone/views/batch.py
index 543d118e..d3912c93 100644
--- a/tailbone/views/batch.py
+++ b/tailbone/views/batch.py
@@ -68,6 +68,7 @@ class BatchMasterView(MasterView):
     rows_downloadable = True
     refreshable = True
     refresh_after_create = False
+    edit_with_rows = True
 
     def __init__(self, request):
         super(BatchMasterView, self).__init__(request)
@@ -296,23 +297,24 @@ class BatchMasterView(MasterView):
         if batch.executed:
             return self.redirect(self.get_action_url('view', batch))
 
-        grid = self.make_row_grid(batch=batch)
+        if self.edit_with_rows:
+            grid = self.make_row_grid(batch=batch)
 
-        # If user just refreshed the page with a reset instruction, issue a
-        # redirect in order to clear out the query string.
-        if self.request.GET.get('reset-to-default-filters') == 'true':
-            return self.redirect(self.request.current_route_url(_query=None))
+            # If user just refreshed the page with a reset instruction, issue a
+            # redirect in order to clear out the query string.
+            if self.request.GET.get('reset-to-default-filters') == 'true':
+                return self.redirect(self.request.current_route_url(_query=None))
 
-        if self.request.params.get('partial'):
-            self.request.response.content_type = b'text/html'
-            self.request.response.text = grid.render_grid()
-            return self.request.response
+            if self.request.params.get('partial'):
+                self.request.response.content_type = b'text/html'
+                self.request.response.text = grid.render_grid()
+                return self.request.response
 
         form = self.make_form(batch)
         if self.request.method == 'POST':
             if form.validate():
                 self.save_edit_form(form)
-                self.request.session.flash("{0} {1} has been updated.".format(
+                self.request.session.flash("{} has been updated: {}".format(
                     self.get_model_title(), self.get_instance_title(batch)))
                 return self.redirect_after_edit(batch)
 
@@ -322,16 +324,23 @@ class BatchMasterView(MasterView):
             'instance_deletable': self.deletable_instance(batch),
             'form': form,
             'batch': batch,
-            'rows_grid': grid,
             'execute_title': self.get_execute_title(batch),
             'execute_enabled': self.executable(batch),
         }
 
+        if self.edit_with_rows:
+            context['rows_grid'] = grid
         if context['execute_enabled'] and self.has_execution_options:
             context['rendered_execution_options'] = self.render_execution_options(batch)
 
         return self.render_to_response('edit', context)
 
+    def rows_creatable_for(self, batch):
+        """
+        Only allow creating new rows on a batch if it hasn't yet been executed.
+        """
+        return not batch.executed
+
     def create_row(self):
         """
         Only allow creating a new row if the batch hasn't yet been executed.
@@ -344,9 +353,11 @@ class BatchMasterView(MasterView):
 
     def make_default_row_grid_tools(self, batch):
         if self.rows_creatable and not batch.executed:
-            link = tags.link_to("Create a new {}".format(self.get_row_model_title()),
-                                self.get_action_url('create_row', batch))
-            return HTML.tag('p', c=link)
+            permission_prefix = self.get_permission_prefix()
+            if self.request.has_perm('{}.create_row'.format(permission_prefix)):
+                link = tags.link_to("Create a new {}".format(self.get_row_model_title()),
+                                    self.get_action_url('create_row', batch))
+                return HTML.tag('p', c=link)
 
     def make_batch_row_grid_tools(self, batch):
         if not batch.executed:
@@ -358,12 +369,13 @@ class BatchMasterView(MasterView):
 
     def redirect_after_edit(self, batch):
         """
-        Redirect back to edit batch page after editing a batch, unless the
-        refresh flag is set, in which case do that.
+        If refresh flag is set, do that; otherwise go (back) to view/edit page.
         """
         if self.request.params.get('refresh') == 'true':
             return self.redirect(self.get_action_url('refresh', batch))
-        return self.redirect(self.request.current_route_url())
+        if self.edit_with_rows:
+            return self.redirect(self.get_action_url('edit', batch))
+        return self.redirect(self.get_action_url('view', batch))
 
     def delete_instance(self, batch):
         """
diff --git a/tailbone/views/master.py b/tailbone/views/master.py
index ecbc357e..0d0e096b 100644
--- a/tailbone/views/master.py
+++ b/tailbone/views/master.py
@@ -1078,6 +1078,7 @@ class MasterView(View):
             'instance_title': self.get_row_instance_title(row),
             'instance_editable': self.row_editable(row),
             'instance_deletable': self.row_deletable(row),
+            'rows_creatable': self.rows_creatable and self.rows_creatable_for(parent),
             'model_title': self.get_row_model_title(),
             'model_title_plural': self.get_row_model_title_plural(),
             'parent_model_title': self.get_model_title(),
@@ -1088,6 +1089,13 @@ class MasterView(View):
             'action_url': self.get_row_action_url,
             'form': form})
 
+    def rows_creatable_for(self, instance):
+        """
+        Returns boolean indicating whether or not the given instance should
+        allow new rows to be added to it.
+        """
+        return True
+
     def edit_row(self):
         """
         View for editing an existing model record.
diff --git a/tailbone/views/purchases/batch.py b/tailbone/views/purchases/batch.py
index 7d8db1d1..3b21f21e 100644
--- a/tailbone/views/purchases/batch.py
+++ b/tailbone/views/purchases/batch.py
@@ -51,6 +51,7 @@ class PurchaseBatchView(BatchMasterView):
     url_prefix = '/purchases/batches'
     rows_creatable = True
     rows_editable = True
+    edit_with_rows = False
 
     def _preconfigure_grid(self, g):
         super(PurchaseBatchView, self)._preconfigure_grid(g)
@@ -65,6 +66,10 @@ class PurchaseBatchView(BatchMasterView):
                                            default_active=True, default_verb='contains')
         g.sorters['buyer'] = g.make_sorter(model.Person.display_name)
 
+        if self.request.has_perm('purchases.batch.execute'):
+            g.filters['complete'].default_active = True
+            g.filters['complete'].default_verb = 'is_true'
+
         g.date_ordered.set(label="Ordered")
 
     def configure_grid(self, g):
@@ -82,26 +87,31 @@ class PurchaseBatchView(BatchMasterView):
 
     def _preconfigure_fieldset(self, fs):
         super(PurchaseBatchView, self)._preconfigure_fieldset(fs)
+        fs.vendor.set(renderer=forms.renderers.VendorFieldRenderer)
+        fs.buyer.set(renderer=forms.renderers.EmployeeFieldRenderer)
         fs.po_number.set(label="PO Number")
-        fs.po_total.set(label="PO Total")
+        fs.po_total.set(label="PO Total", readonly=True)
 
     def configure_fieldset(self, fs):
         fs.configure(
             include=[
+                fs.id,
                 fs.store,
-                fs.vendor.with_renderer(forms.renderers.VendorFieldRenderer),
-                fs.buyer.with_renderer(forms.renderers.EmployeeFieldRenderer),
+                fs.vendor,
+                fs.buyer,
                 fs.date_ordered,
                 fs.po_number,
                 fs.po_total,
                 fs.created,
                 fs.created_by,
+                fs.complete,
                 fs.executed,
                 fs.executed_by,
             ])
 
         if self.creating:
             del fs.po_total
+            del fs.complete
 
             # default store may be configured
             store = self.rattail_config.get('rattail', 'store')
@@ -119,6 +129,10 @@ class PurchaseBatchView(BatchMasterView):
             # default order date is today
             fs.model.date_ordered = localtime(self.rattail_config).date()
 
+        elif self.editing:
+            fs.store.set(readonly=True)
+            fs.vendor.set(readonly=True)
+
     def template_kwargs_view(self, **kwargs):
         kwargs = super(PurchaseBatchView, self).template_kwargs_view(**kwargs)
         vendor = kwargs['batch'].vendor