form to edit multiple entities, use it in DeleteConfForm tls-sprint
authorsylvain.thenault@logilab.fr
Thu, 19 Feb 2009 22:48:50 +0100
branchtls-sprint
changeset 869 168ad6d424d1
parent 868 5d993a0c794c
child 870 0b31dbac6459
form to edit multiple entities, use it in DeleteConfForm
web/form.py
web/test/unittest_form.py
web/views/baseforms.py
--- a/web/form.py	Thu Feb 19 22:17:39 2009 +0100
+++ b/web/form.py	Thu Feb 19 22:48:50 2009 +0100
@@ -20,7 +20,7 @@
 from cubicweb.common.registerers import accepts_registerer
 from cubicweb.web import stdmsgs
 from cubicweb.web.httpcache import NoHTTPCacheManager
-from cubicweb.web.controller import redirect_params
+from cubicweb.web.controller import NAV_FORM_PARAMETERS, redirect_params
 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
 
 
@@ -469,9 +469,9 @@
                 allfields += base._fields_
         clsfields = (item for item in classdict.items()
                      if isinstance(item[1], Field))
-        for name, field in sorted(clsfields, key=lambda x: x[1].creation_rank):
+        for fieldname, field in sorted(clsfields, key=lambda x: x[1].creation_rank):
             if not field.name:
-                field.set_name(name)
+                field.set_name(fieldname)
             allfields.append(field)
         classdict['_fields_'] = allfields
         return super(metafieldsform, mcs).__new__(mcs, name, bases, classdict)
@@ -481,25 +481,40 @@
     __metaclass__ = metafieldsform
     
     def __init__(self, req, id=None, title=None, action='edit',
-                 redirect_path=None):
+                 onsubmit="return freezeFormButtons('%s');",
+                 cssclass=None, cssstyle=None, cwtarget=None, buttons=None,
+                 redirect_path=None, set_error_url=True, copy_nav_params=False):
         self.req = req
         self.id = id or 'form'
         self.title = title
         self.action = action
+        self.onsubmit = onsubmit
+        self.cssclass = cssclass
+        self.cssstyle = cssstyle
+        self.cwtarget = cwtarget
         self.redirect_path = None
         self.fields = list(self.__class__._fields_)
-        self.fields.append(TextField(name='__errorurl', widget=HiddenInput,
-                                     initial=req.url()))
+        if set_error_url:
+            self.form_add_hidden('__errorurl', req.url())
+        if copy_nav_params:
+            for param in NAV_FORM_PARAMETERS:
+                value = req.form.get(param)
+                if value:
+                    self.form_add_hidden('__errorurl', initial=value)
+        self.buttons = buttons or []
         self.context = {}
         
     @property
     def form_needs_multipart(self):
         return any(field.needs_multipart for field in self.fields) 
 
+    def form_add_hidden(self, name, value=None, **kwargs):
+        self.fields.append(TextField(name=name, widget=HiddenInput,
+                                     initial=value, **kwargs))
+
     def form_render(self, **values):
         renderer = values.pop('renderer', FormRenderer())
-        self.form_build_context(values)
-        return renderer.render(self)
+        return renderer.render(self, values)
 
     def form_build_context(self, values):
         self.context = context = {}
@@ -563,39 +578,43 @@
         return u'<input class="validateButton" type="reset" value="%s" tabindex="%s"/>' % (
             label, tabindex or 4)
 
-    
+    def form_buttons(self):
+        return self.buttons
+
+   
 class EntityFieldsForm(FieldsForm):
     def __init__(self, *args, **kwargs):
         kwargs.setdefault('id', 'entityForm')
+        self.entity = kwargs.pop('entity', None)
         super(EntityFieldsForm, self).__init__(*args, **kwargs)
-        self.fields.append(TextField(name='__type', widget=HiddenInput))
-        self.fields.append(TextField(name='eid', widget=HiddenInput,
-                                     eidparam=False))
+        self.form_add_hidden('__type')
+        self.form_add_hidden('eid', eidparam=False)
         
     def form_render(self, entity, **values):
+        self.form_add_entity_hiddens(entity.e_schema)
         self.entity = entity
-        eschema = self.entity.e_schema
+        return super(EntityFieldsForm, self).form_render(**values)
+
+    def form_add_entity_hiddens(self, eschema):
         for field in self.fields[:]:
             fieldname = field.name
             if fieldname != 'eid' and (
                 (eschema.has_subject_relation(fieldname) or
-                eschema.has_object_relation(fieldname))):
-                self.fields.append(self.build_hidden_field(field))
-        return super(EntityFieldsForm, self).form_render(**values)
+                 eschema.has_object_relation(fieldname))):
+                self.fields.append(self.form_entity_hidden_field(field))
 
-    def build_hidden_field(self, field):
+    def form_entity_hidden_field(self, field):
         """returns the hidden field which will indicate the value
         before the modification
         """
         # Only RelationField has a `role` attribute, others are used
         # to describe attribute fields => role is 'subject'
-        role = getattr(field, 'role', 'subject')
-        if role == 'subject':
+        if getattr(field, 'role', 'subject') == 'subject':
             name = 'edits-%s' % field.name
         else:
             name = 'edito-%s' % field.name
         return HiddenInitialValueField(field, name=name)
-    
+        
     def form_field_value(self, field, values):
         """look for field's value with the following rules:
         1. handle special __type and eid fields
@@ -665,45 +684,74 @@
             return zip((entity.req._(v) for v in choices), choices)
         return zip(choices, choices)
 
-    
+
+class MultipleFieldsForm(FieldsForm):
+    def __init__(self, *args, **kwargs):
+        super(MultipleFieldsForm, self).__init__(*args, **kwargs)
+        self.forms = []
+
+    def form_add_subform(self, subform):
+        self.forms.append(subform)
+        
 # form renderers ############
 
 class FormRenderer(object):
     
-    def render(self, form):
+    def render(self, form, values):
         data = []
         w = data.append
         # XXX form_needs_multipart
-        w(u'<form action="%s" onsubmit="return freezeFormButtons(\'%s\');" method="post" id="%s">'
-          % (form.req.build_url(form.action), form.id, form.id))
+        print 'render', form
+        w(self.open_form(form))
         w(u'<div id="progress">%s</div>' % _('validating...'))
         w(u'<fieldset>')
         w(tags.input(type='hidden', name='__form_id', value=form.id))
         if form.redirect_path:
             w(tags.input(type='hidden', name='__redirect_path', value=form.redirect_path))
-        self.render_fields(w, form)
+        self.render_fields(w, form, values)
         self.render_buttons(w, form)
         w(u'</fieldset>')
         w(u'</form>')
         return '\n'.join(data)
 
-    def render_fields(self, w, form):
+    def open_form(self, form):
+        if form.form_needs_multipart:
+            enctype = 'multipart/form-data'
+        else:
+            enctype = 'application/x-www-form-urlencoded'
+        tag = ('<form action="%s" method="post" id="%s" enctype="%s"' % (
+            html_escape(form.req.build_url(form.action)), form.id, enctype))
+        if form.onsubmit:
+            tag += ' onsubmit="%s"' % html_escape(form.onsubmit)
+        if form.cssstyle:
+            tag += ' style="%s"' % html_escape(form.cssstyle)
+        if form.cssclass:
+            tag += ' class="%s"' % html_escape(form.cssclass)
+        if form.cwtarget:
+            tag += ' cubicweb:target="%s"' % html_escape(form.cwtarget)
+        return tag + '>'
+        
+    def render_fields(self, w, form, values):
+        form.form_build_context(values)
         fields = form.fields[:]
         for field in form.fields:
             if isinstance(field.widget, HiddenInput):
                 w(field.render(form))
                 fields.remove(field)
-        w(u'<table>')
-        for field in fields:
-            w(u'<tr>')
-            w('<th>%s</th>' % self.render_label(form, field))
-            w(u'<td style="width:100%;">')
-            w(field.render(form))
-            w(u'</td></tr>')
-        w(u'</table>')
-
+        if fields:
+            w(u'<table>')
+            for field in fields:
+                w(u'<tr>')
+                w('<th>%s</th>' % self.render_label(form, field))
+                w(u'<td style="width:100%;">')
+                w(field.render(form))
+                w(u'</td></tr>')
+            w(u'</table>')
+        for childform in getattr(form, 'forms', []):
+            self.render_fields(w, childform, values)
+            
     def render_buttons(self, w, form):
-        for button in form.buttons():
+        for button in form.form_buttons():
             w(button)
         
     def render_label(self, form, field):
--- a/web/test/unittest_form.py	Thu Feb 19 22:17:39 2009 +0100
+++ b/web/test/unittest_form.py	Thu Feb 19 22:48:50 2009 +0100
@@ -1,29 +1,35 @@
 from logilab.common.testlib import unittest_main, mock_object
-from cubicweb.devtools.apptest import EnvBasedTC
+from cubicweb.devtools.testlib import WebTest
 from cubicweb.web.form import *
 from cubicweb.web.views.baseforms import ChangeStateForm
 
+
 class CustomChangeStateForm(ChangeStateForm):
     hello = IntField(name='youlou')
     creation_date = DateTimeField(widget=DateTimePicker)
+
     
-class EntityFieldsFormTC(EnvBasedTC):
+class EntityFieldsFormTC(WebTest):
 
     def setUp(self):
         super(EntityFieldsFormTC, self).setUp()
         self.req = self.request()
         self.entity = self.user(self.req)
         
-    def test(self):
+    def test_form_inheritance(self):
+        form = CustomChangeStateForm(self.req, redirect_path='perdu.com')
+        self.assertEquals(form.form_render(self.entity, state=123),
+                          ''' ''')
+
+    def test_change_state_form(self):
         form = ChangeStateForm(self.req, redirect_path='perdu.com')
         self.assertEquals(form.form_render(self.entity, state=123),
                           ''' ''')
 
-    def test_form_inheritance(self):
-        form = CustomChangeStateForm(self.req, redirect_path='perdu.com')
-        self.assertEquals(form.form_render(self.entity, state=123),
-                          ''' ''')
-        
+    def test_delete_conf_form_multi(self):
+        rset = self.execute('EGroup X')
+        self.assertEquals(self.view('deleteconf', rset).source,
+                          '')
         
 if __name__ == '__main__':
     unittest_main()
--- a/web/views/baseforms.py	Thu Feb 19 22:17:39 2009 +0100
+++ b/web/views/baseforms.py	Thu Feb 19 22:48:50 2009 +0100
@@ -20,6 +20,7 @@
                                 non_final_entity, accepts_etype_compat)
 from cubicweb.utils import make_uid
 from cubicweb.view import View, EntityView
+from cubicweb.common import tags
 from cubicweb.common.uilib import cut
 from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
 from cubicweb.web.controller import NAV_FORM_PARAMETERS
@@ -28,7 +29,11 @@
 
 _ = unicode
 
-class DeleteConfForm(FormMixIn, EntityView):
+from cubicweb.web.form import MultipleFieldsForm, EntityFieldsForm, TextField, \
+     RichTextField, HiddenInput
+
+
+class DeleteConfForm(EntityView):
     id = 'deleteconf'
     title = _('delete')
     domid = 'deleteconf'
@@ -39,67 +44,47 @@
     
     def call(self):
         """ask for confirmation before real deletion"""
-        _ = self.req._
-        self.req.add_js('cubicweb.edition.js')
-        self.w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n' % _('this action is not reversible!'))
+        req, w = self.req, self.w
+        _ = req._
+        req.add_js('cubicweb.edition.js')
+        w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
+          % _('this action is not reversible!'))
         # XXX above message should have style of a warning
-        self.w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
-        if self.onsubmit:
-            self.w(u'<form id="deleteconf" action="%s" onsubmit="%s" method="post">'
-                   % (self.build_url(), self.onsubmit))
-        else:
-            self.w(u'<form id="deleteconf" action="%s" method="post">'
-                   % (self.build_url()))
-            
-        self.w(u'<fieldset>\n')
-        self.display_rset()
-        #self.w(u'<input type="hidden" name="rql" value="%s"/>' % self.req.form['rql'])
-        self.w(u'<input type="hidden" name="__form_id" value="%s"/>' % self.id)
-        self.w(self.button_delete(label=stdmsgs.YES))
-        self.w(self.button_cancel(label=stdmsgs.NO))
-        for param in NAV_FORM_PARAMETERS:
-            value = self.req.form.get(param)
-            if value:
-                self.w(u'<input type="hidden" name="%s" value="%s"/>' % (param, value))
-        self.w(u'</fieldset></form>\n')
-
-    def display_rset(self):
-        self.w(u'<ul>\n')
+        w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
+        form = MultipleFieldsForm(req, id='deleteconf', action=self.build_url(),
+                                  onsubmit=self.onsubmit, copy_nav_params=True)
+        form.buttons.append(form.button_delete(label=stdmsgs.YES))
+        form.buttons.append(form.button_cancel(label=stdmsgs.NO))
         done = set()
+        w(u'<ul>\n')
         for i in xrange(self.rset.rowcount):
             if self.rset[i][0] in done:
                 continue
             done.add(self.rset[i][0])
-            self.cell_call(i, 0)
-        self.w(u'</ul>\n')
-        
-    def cell_call(self, row, col):
-        entity = self.entity(row, col)
-        self.w(u'<li>')
-        self.w(u'<input type="hidden" name="eid" value="%s" />' % entity.eid)
-        self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
-               % (eid_param('__type', entity.eid), self.rset.description[row][0]))
-        self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
-        # don't use outofcontext view or any other that may contain inline edition form
-        self.w(html_escape(entity.view('textoutofcontext')))
-        self.w(u'</a>')
-        self.w(u'</li>')
+            entity = self.rset.get_entity(i, 0)
+            subform = EntityFieldsForm(req, set_error_url=False,
+                                       entity=entity)
+            form.form_add_subform(subform)
+            # don't use outofcontext view or any other that may contain inline edition form
+            w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
+                                      href=entity.absolute_url()))
+        w(u'</ul>\n')
+        w(form.form_render())
 
 
-from cubicweb.web.form import EntityFieldsForm, TextField, RichTextField, HiddenInput
-
 class ChangeStateForm(EntityFieldsForm):
     state = TextField(widget=HiddenInput)
-    __method = TextField(widget=HiddenInput, initial='set_state')
+    __method = TextField(name='__method', initial='set_state', widget=HiddenInput)
     trcomment = RichTextField(eidparam=True)
 
-    def buttons(self):
-        return [self.button_ok(label=self.req._(stdmsgs.YES),
+    def form_buttons(self):
+        return [self.button_ok(label=stdmsgs.YES,
                                tabindex=self.req.next_tabindex()),
-                self.button_cancel(label=self.req._(stdmsgs.NO),
+                self.button_cancel(label=stdmsgs.NO,
                                    tabindex=self.req.next_tabindex())]
+
         
-class ChangeStateFormView(FormMixIn, EntityView):
+class ChangeStateFormView(EntityView):
     id = 'statuschange'
     title = _('status change')
 
@@ -122,46 +107,6 @@
         form = ChangeStateForm(redirect_path=self.redirectpath(entity)) # self.vreg.select_form('changestateform')
         self.w(form.form_render(req, entity, state=dest.eid))
 
-        
-#         self.w(u'<form action="%s" onsubmit="return freezeFormButtons(\'entityForm\');" method="post" id="entityForm">\n'
-#                % self.build_url('edit'))
-#         self.w(u'<div id="progress">%s</div>' % _('validating...'))
-#         self.w(u'<fieldset>\n')
-#         #self.w(u'<input id="errorurl" type="hidden" name="__errorurl" value="%s"/>\n'
-#         #       % html_escape(self.req.url()))
-#         self.w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
-#         self.w(u'<input type="hidden" name="eid" value="%s" />' % eid)
-#         self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
-#                % (eid_param('__type', eid), entity.e_schema))
-#         self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
-#                % (eid_param('state', eid), dest.eid))
-#         self.w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n'
-#                % html_escape(self.redirectpath(entity)))
-#         self.fill_form(entity, state, dest)
-#         self.w(u'<input type="hidden" name="__method" value="set_state"/>\n')
-#         self.w(self.button_ok(label=stdmsgs.YES, tabindex=self.req.next_tabindex()))
-#         self.w(self.button_cancel(label=stdmsgs.NO, tabindex=self.req.next_tabindex()))
-#         self.w(u'</fieldset>\n')
-#         self.w(u'</form>')
-        
-#     def fill_form(self, entity, state, dest):
-#         # hack to use the widget for comment_format
-#         trinfo = self.vreg.etype_class('TrInfo')(self.req, None)
-#         # widget are cached, copy it since we want to modify its name attribute
-#         wdg = trinfo.get_widget('comment_format')
-#         wdg.name = 'trcommentformat'
-#         # set a value in entity to avoid lookup for a non existant attribute...
-#         trinfo['trcommentformat'] = u''
-#         # comment format/content have to be grouped using the original entity eid
-#         wdg.rname = eid_param('trcommentformat', entity.eid)
-#         self.w(wdg.render_label(trinfo))
-#         self.w(wdg._edit_render(trinfo))
-#         self.w(u'<br/>\n')
-#         cformname = eid_param('trcomment', entity.eid)
-#         self.w(u'<label for="%s">%s</label>\n' % (cformname, self.req._('comment:')))
-#         self.w(u'<textarea rows="10" cols="80" name="%s" tabindex="%s"></textarea><br/>\n'
-#                % (cformname, self.req.next_tabindex()))
-
     def redirectpath(self, entity):
         return entity.rest_path()