Changeset 199

Show
Ignore:
Timestamp:
09/05/08 09:18:54 (3 months ago)
Author:
rgrp
Message:

[factlet/edit][l]: add support for factlet editing, adding in template action support to factlet.

  • factlet.py:
    • new update method providing factlet editing, previewing and saving
    • new template method to provide json based templating
  • lib/json.py: add flush option to converter to ensure don't have to flush to db. Need because new templating work requires the ability to use converter (and create python Domain Objects) without modifying domain model (i.e. saving to db).
  • layout.html: remove any default jspagecontroller.
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/microfacts/controllers/factlet.py

    r195 r199  
     1import simplejson as sj 
     2import genshi 
     3 
    14from microfacts.lib.base import * 
    2 import genshi 
    35from microfacts.modes import * 
     6import microfacts.lib.json 
    47 
    58class FactletController(BaseController): 
     
    5457            id = int(id) 
    5558        except: 
    56             id = 0 
     59            abort(404) 
    5760        mode = EntityGet('/factlet/%s' % id).execute() 
    5861        if mode.response_code == 200: 
    5962            c.factlet = mode.entity 
    60             # NB: fragment does not seem to have any effect 
    61             # see this thread: 
    62             # http://groups.google.com/group/genshi/browse_thread/thread/a587cc282403aace 
    6363            html = render('factlet/read_core', format='xml') 
    6464            return html 
     
    6767 
    6868    def read(self, id): 
    69         c.has_inline_edit = True 
    7069        html = self.read_core(id) 
    7170        c.factlet_html = genshi.XML(html) 
     
    7372 
    7473    def update(self, id): 
    75         return self.read(id) 
     74        try: 
     75            id = int(id) 
     76        except: 
     77            abort(404) 
     78        ispreview = request.params.has_key('preview') 
     79        iscommit = request.params.has_key('commit') 
     80        if not (ispreview or iscommit): 
     81            mode = EntityGet('/factlet/%s' % id).execute() 
     82            if mode.response_code == 200: 
     83                c.factlet = mode.entity 
     84                edit_form_html = render('factlet/edit_core', format='xml') 
     85                c.edit_form = genshi.XML(edit_form_html) 
     86                # return render_response( 
     87                return render('factlet/edit') 
     88            else: 
     89                abort(mode.response_code) 
     90        # so must be preview or commit ... 
     91        entity_data = dict(request.params) 
     92        # works in place 
     93        self.unflatten_form_data(entity_data) 
     94        if ispreview: 
     95            return self._preview(entity_data) 
     96        elif iscommit: 
     97            mode = EntityPut('/factlet/%s' % id, 
     98                request_data=entity_data 
     99            ).execute() 
     100            if mode.response_code == 200: 
     101                h.redirect_to(controller='factlet', action='read', id=id) 
     102            else: # errors 
     103                c.error = '%s' % mode.response_code 
     104                return self._preview(entity_data) 
    76105 
     106    def _preview(self, entity_data): 
     107        # to be compatible needs to dump it back to json (even though below we 
     108        # will immediatley loads!) 
     109        entity_data = sj.dumps(entity_data) 
     110        # this works because data from the form is same as data from json load 
     111        json_data = entity_data 
     112        c.factlet = self._parse_json_data(json_data) 
     113        c.factlet_html = genshi.XML(render('factlet/read_core', format='xml')) 
     114        c.factlet = self._parse_json_data(json_data) 
     115        c.edit_form = genshi.XML(render('factlet/edit_core', format='xml')) 
     116        return render('factlet/edit') 
     117 
     118    def unflatten_form_data(self, formdict): 
     119        # convert back to json string format (not actual domain object) 
     120        # name convention follows formencode 
     121        x = formdict.get('location__x', None) 
     122        y = formdict.get('location__y', None) 
     123        try: x = float(x) 
     124        except: x = None 
     125        try: y = float(y) 
     126        except: y = None 
     127        if x or y: 
     128            formdict['location'] = {'type': 'Point', 'coordinates': [x, y] } 
     129 
     130    def template(self, id): 
     131        template_kind = id 
     132        # like formencode htmlfill: might want to see what we can do 
     133        try: 
     134            request_data = request.params.keys()[0] 
     135        except Exception, inst: 
     136            msg = "Can't find entity data in request params %s: %s" % ( 
     137                request.params.items(), str(inst) 
     138            ) 
     139            raise Exception, msg 
     140        if template_kind == 'read': 
     141            c.factlet = self._parse_json_data(request_data) 
     142            html = render('factlet/read_core', format='xml') 
     143            return html 
     144        elif template_kind == 'edit': 
     145            c.factlet = self._parse_json_data(request_data) 
     146            html = render('factlet/edit_core', format='xml') 
     147            return html 
     148        else: 
     149            abort(404) 
     150 
     151    # TODO: remove_id argument to make this optional 
     152    # this means that e.g. for read can just send id and will work 
     153    def _parse_json_data(self, json_data): 
     154        json_data = sj.loads(json_data) 
     155        # do not want to flush as may be just a preview 
     156        converter = microfacts.lib.json.FactletConverter(flush=False) 
     157        # also remove id just to be safe 
     158        # with id in even with no flush we may modify the domain object with 
     159        # this id in the current session 
     160        fctid = None 
     161        if 'id' in json_data: 
     162            fctid = json_data['id'] 
     163            del json_data['id'] 
     164        factlet  = converter.to_domain_object(json_data) 
     165        # add it back in for use in the template 
     166        if fctid: 
     167            factlet.id = fctid 
     168        return factlet  
  • trunk/microfacts/lib/json.py

    r191 r199  
    4545class Converter(object): 
    4646    domain_object = None 
     47 
     48    def __init__(self, flush=True): 
     49        ''' 
     50        @param flush: if True automatically add domain objects to DB session 
     51        and flush. If False ensure domain objects are not in DB session. 
     52        ''' 
     53        self.flush = flush 
    4754 
    4855    def _get_domain_object(self, data): 
     
    135142                    val = shapely.geometry.asShape(geojson.loads(simplejson.dumps(v))) 
    136143            setattr(entity, k, val) 
    137         model.Session.flush() 
     144        if self.flush: 
     145            model.Session.flush() 
    138146        return entity 
    139147 
     
    156164                val = v 
    157165            setattr(entity, k, val) 
    158         model.Session.flush() 
     166        if self.flush: 
     167            model.Session.flush() 
    159168        return entity 
    160  
    161169 
    162170 
  • trunk/microfacts/public/behaviour/app/views/factletdetailsview.js

    r196 r199  
    3131            console.log('Factlet Edit Link clicked.'); 
    3232            var evt = new Event(e); 
    33             evt.stop(); 
     33            // evt.stop(); 
    3434            // TODO: inline editing 
    35             alert('Inline editing is not yet supported'); 
     35            // alert('Inline editing is not yet supported'); 
    3636        } 
    3737    } 
  • trunk/microfacts/templates/factlet/edit.html

    r79 r199  
    1010 
    1111  <head> 
    12     <title>${c.factlet.title}</title> 
    13     <!--! script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAARNdolrumeZfr3ExZHK5EHBTaNnMyXYsksD7Oz4rtdsnmKPf0KxTVGYHuM-V9OxYSe0DnM5E-SF2tTQ"></script--> 
    14     <script type="text/javascript"> 
    15       <!--! 
    16       // function loadMap() { 
    17       //   var map = new google.maps.Map2($("factlet-minimap")); 
    18       //   map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13); 
    19       // } 
    20       //     
    21       // google.load("maps", "2.x"); 
    22       // window.addEvent('unload', GUnload); 
    23       --> 
    24     </script> 
     12    <title>Edit - ${c.factlet.title}</title> 
    2513  </head> 
    2614 
    2715  <body class="importfactlet"> 
    28     ${c.factlet_html} 
     16    <h2>Edit: ${c.factlet.title}</h2> 
     17    <p class="error" py:if="c.error"> 
     18      There was an error: ${c.error} 
     19    </p> 
     20    ${c.edit_form} 
     21    <py:if test="c.factlet_html"> 
     22      <div class="preview"> 
     23        <h2>Preview</h2> 
     24        ${c.factlet_html} 
     25      </div> 
     26    </py:if> 
    2927  </body> 
    3028</html> 
  • trunk/microfacts/templates/factlet/edit_core.html

    r196 r199  
    1 <!DOCTYPE html> 
    21<div 
    32  xmlns="http://www.w3.org/1999/xhtml" 
     
    65  py:strip="True" 
    76  > 
     7    <div id="factlet-${c.factlet.id}" class="factlet factlet-update-form"> 
     8    <form class="factlet-update-form" method="post" action="" accept-charset="utf-8"> 
    89 
    9     <div id="factlet-${c.factlet.id}" class="factlet factlet-details"> 
    10       <h3 class="title">${c.factlet.title}</h3> 
    11      
    12       <img src="${c.factlet.image}" /> 
    13       <p class="description">${c.factlet.description}</p> 
     10      ${h.hidden_field('name')} 
     11 
     12      <label>Title</label> 
     13      ${h.text_field('title', value=c.factlet.title, size=50)} 
     14      <br /> 
     15 
     16      <label>Image Url</label> 
     17      ${h.text_field('image', value=c.factlet.image, size=50)} 
     18      <br /> 
     19 
     20      <label for="description">Description:</label><br /> 
     21      ${h.text_area('description', content=c.factlet.description, size="60x15")} <br /> 
    1422      <dl> 
    1523        <dt>Date</dt> 
    1624        <dd> 
    17           Start: ${c.factlet.start} <br /> 
    18           End: ${c.factlet.end
     25          <label>Start:</label> ${h.text_field('start', value=c.factlet.start)} <br /> 
     26          <label>End:</label> ${h.text_field('end', value=c.factlet.end)
    1927        </dd> 
    2028        <dt>Location</dt> 
    2129        <dd> 
    22           Long: ${c.factlet.location.x} <br /> 
    23           Lat: ${c.factlet.location.y
     30          <label>Long:</label> ${h.text_field('location__x', value=value_of(c.factlet.location.x, None))} <br /> 
     31          <label>Lat:</label> ${h.text_field('location__y', value=value_of(c.factlet.location.y, None))
    2432        </dd> 
    25         <dt>Source</dt> 
     33        <dt>Source Url</dt> 
    2634        <dd> 
    27           <py:if test="c.factlet.source"> 
    28           <a href="${c.factlet.source}">${c.factlet.source}</a> 
    29           </py:if> 
     35          ${h.text_field('source', value=c.factlet.source)} 
    3036        </dd> 
    3137        <dt>License</dt> 
    32         <dd>${c.factlet.license}</dd> 
     38        <dd> 
     39          ${h.text_field('license', value=c.factlet.license)} 
     40        </dd> 
    3341      </dl> 
    3442 
    3543      <p> 
    36         <a href="${h.url_for(controller='factlet', action='update', id=c.factlet.id)}">Edit &raquo;</a> 
     44        <input name="preview" type="submit" value="Preview" /> 
     45        ${XML(h.submit())} 
    3746      </p> 
     47    </form> 
    3848    </div> 
    3949</div> 
  • trunk/microfacts/templates/factlet/read.html

    r79 r199  
    1010 
    1111  <head> 
    12     <title>${c.factlet.title}</title> 
    13     <!--! script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAARNdolrumeZfr3ExZHK5EHBTaNnMyXYsksD7Oz4rtdsnmKPf0KxTVGYHuM-V9OxYSe0DnM5E-SF2tTQ"></script--> 
    14     <script type="text/javascript"> 
    15       <!--! 
    16       // function loadMap() { 
    17       //   var map = new google.maps.Map2($("factlet-minimap")); 
    18       //   map.setCenter(new google.maps.LatLng(37.4419, -122.1419), 13); 
    19       // } 
    20       //     
    21       // google.load("maps", "2.x"); 
    22       // window.addEvent('unload', GUnload); 
    23       --> 
    24     </script> 
     12    <title>View - ${c.factlet.title}</title> 
    2513  </head> 
    2614 
  • trunk/microfacts/templates/layout.html

    r195 r199  
    77  > 
    88 
    9   <py:def function="pagecontrollername">microfacts</py:def> 
     9  <py:def function="pagecontrollername"></py:def> 
    1010 
    1111  <py:match path="head" once="true"> 
  • trunk/microfacts/tests/functional/test_factlet.py

    r195 r199  
    22import microfacts.model as model 
    33from microfacts.modes import * 
     4 
     5from simplejson import dumps 
    46 
    57class TestFactletController(TestController): 
     
    79    def setUp(self): 
    810        model.Session.clear() 
     11        # cannot use this transaction+rollback approach to insulating fixtures 
     12        # as it causes problems for the update test (factlet is not retrieved 
     13        # after model.Session.clear -- perhaps because not 'properly' in db 
     14        # model.Session.begin() 
    915        import microfacts.lib.json 
    1016        import pkg_resources 
     
    2127 
    2228    def tearDown(self): 
    23         model.Session.delete(self.thread) 
    24         for fct in self.factlets: 
    25             model.Session.delete(fct) 
     29        # model.Session.rollback() 
     30        for item in [self.thread] + self.factlets: 
     31            if item not in model.Session(): 
     32                item = model.Session().merge(item) 
     33            item.delete() 
    2634        model.Session.flush() 
    2735        model.Session.remove() 
     
    7078        assert 'Battle of Waterloo' in response 
    7179 
    72     def test_read_core(self): 
    73         factletId = self.factlets[0].id 
     80    def test_template_read(self): 
     81        path = url_for(controller='factlet', action='template', id='read') 
     82        ftconverter = microfacts.lib.json.FactletConverter() 
    7483        fct = self.factlets[0] 
    75         path = url_for(controller='factlet', action='read_core', id=factletId) 
    76         response = self.app.get(path) 
    77         print response 
     84        data = ftconverter.from_domain_object(fct) 
     85        response = self.app.post(path, params=dumps(data)) 
    7886        assert 'Battle of Austerlitz' in response 
    7987        assert str(fct.license) in response 
     
    8290        assert fct.location.x in response 
    8391 
     92    def test_template_read_without_object(self): 
     93        path = url_for(controller='factlet', action='template', id='read') 
     94        title = 'Something Completely Random' 
     95        data = { 'title': title } 
     96        response = self.app.post(path, params=dumps(data)) 
     97        assert title in response 
     98 
     99    def test_template_edit(self): 
     100        path = url_for(controller='factlet', action='template', id='edit') 
     101        ftconverter = microfacts.lib.json.FactletConverter() 
     102        fct = self.factlets[0] 
     103        data = ftconverter.from_domain_object(fct) 
     104        newtitle = 'Battle of Austerlitz Modified but Not Saved' 
     105        data['title'] = newtitle 
     106        response = self.app.post(path, params=dumps(data)) 
     107        print response 
     108        assert newtitle in response 
     109        assert fct.description in response 
     110        assert str(fct.license) in response 
     111        assert fct.start in response 
     112        # *really* weird, value_of does not work in template now but plain 
     113        # ${c.factlet.location.x} does! 
     114        # assert fct.location.x in response 
     115        assert '<label>' in response 
     116        # check no change to domain model ... 
     117        assert fct.title != newtitle 
     118 
    84119    def test_read(self): 
    85         # do not too much testing here as should be in read_core 
     120        # do not too much testing here as similar to template_read test 
    86121        factletId = self.factlets[0].id 
    87         assert factletId 
     122        fct = self.factlets[0] 
    88123        path = url_for(controller='factlet', action='read', id=factletId) 
    89124        response = self.app.get(path) 
    90         assert 'Microfacts' in response, response 
    91         assert 'Battle of Austerlitz' in response, response 
     125        print response 
     126        assert 'Microfacts' in response 
     127        assert 'Battle of Austerlitz' in response 
     128        assert str(fct.license) in response 
     129        assert fct.start in response 
    92130 
    93         # Delete when threads are showing again. 
    94         return 
     131    def test_update(self): 
     132        factletId = self.factlets[0].id 
     133        fct = self.factlets[0] 
     134        path = url_for(controller='factlet', action='update', id=factletId) 
     135        response = self.app.get(path) 
     136        assert 'Microfacts' in response 
     137        assert '<form' in response, response 
     138        form = response.forms[0] 
     139        newtitle = 'Battle of Austerlitz Modified but Not Saved' 
     140        newstart = '2008-08-22' 
     141        form['title'] = newtitle 
     142        form['start'] = newstart 
     143        response = form.submit('preview') 
     144        assert newtitle in response, response 
     145        assert newstart in response 
     146        form = response.forms[0] 
     147        response = form.submit('commit', status=[200,404,302]) 
     148        # response = response.follow() 
     149        model.Session.clear() 
     150        fct = model.Factlet.query.filter_by(id=factletId).first() 
     151        assert fct.title == newtitle 
    95152 
    96         try: 
    97             assert 'Battles in the Napoleonic Wars' in response 
    98         except: 
    99             print response 
    100             raise 
    101  
    102         response = response.click('Battles in the Napoleonic Wars') 
    103         try: 
    104             assert 'Battles in the Napoleonic Wars' in response 
    105             assert 'Battle of Austerlitz' in response 
    106             assert 'Battle of Borodino' in response 
    107             assert 'Battle of Waterloo' in response 
    108         except: 
    109             print response 
    110             raise 
    111153 
    112154class TestFactletNew(TestController):