Crosstips.org

My fun Crossword solver project. Crosstips.org & Krysstips.se

Kung Fu

Fujian White Crane Kung Fu

Fry-IT

Fry-IT is the company I work for

Photos

Photoalbum, both old and new.

Zope

What I have and am doing with Zope

Receptsamlingen

In Swedish only. About my "Collection of Recipes" website.

Contact me

My contact details and how to contact me.

 

KungFuPeople.com
Do you train Kung Fu?
Or know someone who does?
Then check out KungFuPeople.com


Mobile version of this page Mobile version of this page

RSS

Hot topics

by Peter Bengtsson: Hi Gael, First of all I can't maintain two ZCatalog instances since the wil...

Massive improvement on sorting a fat list

by Gael Le Mignot: Hello. We are using a quite heavily customized version of an older IssueTra...

Massive improvement on sorting a fat list

by Peter Bengtsson: Thanks for the tip. Sorting 6668 tuples only takes about 0.03 seconds on my...

Massive improvement on sorting a fat list

by Simon Michael: Nice one. pympler looks awesome, thanks for the example....

Massive improvement on sorting a fat list

by Matt Chaput: If you really wanted to be efficient, you could use a generator heapq.nlarg...

Massive improvement on sorting a fat list

by Ross Patterson: It seems like you might be able to do a bit better with generators and the ...

Massive improvement on sorting a fat list

by marc: I just wanted to let you know that there is a problem with ZTinyMCE when ha...

TinyMCE + Zope = ZTinyMCE

by bobpooch: This works great. The key is security.apply(), that was the piece I was mi...

Setting security declarations to Zope classes

by kedai: peter, i <3 custom fields. nw with documentation, even better. now we can...

Custom Fields in IssueTrackerProduct documentation

by betabug: uhm.... ZPL is rather BSD based, not GPL... but anyway, it's definitely muc...

TinyMCE + Zope = ZTinyMCE

Old entries


September, 2009
London Frock Exchange launched
My first Twitter app - KungFuPeople.com
Comparing jsmin and slimmer
Python Code Dojo London - 17 Sep 2009
"Hello John. It's Gordon Brown."
7 of the World's Most Irresponsible Companies

August, 2009
Cgunit - Online Gallery
To sub-select or not sub-select in PostgreSQL
Custom CacheMiddleware that tells Javascript a page is cached in Django
What a super user-friendly menu!
Table Of Countries Showing Drive Direction
The Secret to SEO Search Engine Optimization
Calling all kung fu people - kungfupeople.com
Google Reverse Geocoding vs. GeoNames
gg - wrapping git-grep
Public calendars on Google Calendar
More optimization of Peterbe.com - CSS sprites

2009
2008
2007
2006
2005
2004
2003

 

You're viewing blogs from Zope only.

View all different categories

28th of February

Massive improvement on sorting a fat list

IssueTrackerMassContainer is a simple Zope product that is used to put a bunch of IssueTrackerProduct instances into. It doesn't add much apart from a nice looking dashboard that lists all recent issues and then with an AJAX poll it keeps updating automatically.

But what it was doing was it recursively put together all issues across all issue trackers, sorting them and then returning only the first 20. Fine, but once the numbers start to add up it can become a vast sort operation to deal with.

In my local development copy of 814 issues, by the use of pympler and time() I was able to go from 7 Mb taking 2 seconds down to using only 8 Kb and taking 0.05 seconds.


>Read the whole text (409 more words)

4th of June

Custom Fields in IssueTrackerProduct documentation written

Custom Fields in IssueTrackerProduct documentation written The Custom Fields feature started as a consultancy job in which we agreed the work can be open sourced as part of IssueTrackerProduct so I never got around to write an sensible high level documentation for it. Now I have! From the news piece about it:

"Custom Fields was a feature that was released almost a year ago but didn't have much documentation. Especially easy documentation that describes what it is and how it can be used. That has changed now.

In Custom Fields it is now described what they are and how they can become useful to you. It's such a powerful tool that very few "competing" issue/bug tracking systems can offer."

The written documentation is here: Custom Fields

Feedback appreciated.

15th of April

Lesson learned: Unicodifying request variables in Zope

This cost me a good hour of debugging so I thought I'd share it in case anybody else stumbles across the same problem. In the end, to solve my problem I had to add debug statements to StringIO.py to be able to find out where in my Zope Page Template a non-unicode string with non-ascii characters appeared and messed things up.

The error I was getting was this, which I suspect several Zope developers have encountered before:

 UnicodeDecodeError: \
 'ascii' codec can't decode byte 0xc3 in position 46: ordinal not in range(128)

The traceback only mentions files in the innards of ZPT of which none you can really do anything about. We all know that the key to avoid Unicode error is to be consistent. You can do this:

 >>> '\xc3' + 'string'
 '\xc3string'
 >>> u'\xc3' + u'string'
 u'\xc3string'

But you can't do this:

 >>> '\xc3' + u'string'
 Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
 UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 0: 
 ordinal not in range(128)

So, how did I get these non-unicode strings into my application in first place. Simple, I have a search parameter q and end up with a URL like this:

 /bla/bla?q=régime

And the template had this little innocent piece of code:

 <input tal:attributes="value request/q"/>

That's what f'ed everything up. So, I ended up having to add this:

 <input tal:attributes="value python:context.unicodify(request['q'])"/> 

With this little helper function in the base class:

 def unicodify(self, s):
    if isinstance(s, str):
        return unicode(s, 'utf8')
    return s

So, hopefully by writing this it will help someone else making the same trivial mistake and not wasting their evening with sporadic print statements all over their frameworks code.

1st of October

When '_properties' gets stuck as a persistent attribute

Doing some on-site consulting on an old Zope CMS that has been developed by many different developers over many years. It's pretty good and has lots of powerful features but over the years certain things have been allowed to slip. One problem was that you couldn't click the "Properties" tab. The reason was that it was trying to fetch properties that didn't exist anymore. What had happened was that the class attribute _properties (which is used by the "Properties" tab in the ZMI) had been stored as a persistent attribute. Here's how to solve that:

 def manage_fixPropertiesProblem(self):
     """ fix so _properties becomes a class attribute instead """
     if '_properties' in self.__dict__.keys():
         del self._properties

     return "Awesome!"

30th of May

zope-memory-readings - Tracking Zope2's memory usage by URL

zope-memory-readings - Tracking Zope2's memory usage by URL I've just released a new little project in Python for tracking memory usage in Zope2 applications with the added benefit that you can hopefully see what URL causes which memory usage "jumps". Hopefully this can help Zope2 developers find out what causes RAM bloat but can also help in helping you optimize your application by early in the development process find out what uses too much RAM. I wouldn't be surprised that there is already a program that does something like this. I've just never seen one. Also by putting this out as an Open Source project and blogging about it hopefully more clever people than me will come forward and point out the right way to do things.

I've also used Google Code this time to manage the project. I've used it before but only for hosting a public SVN for the IssueTrackerProduct SVN. I have to say that I was quite impressed with Google Code this second time. I think it's still fundamentally wrong to confuse people with by offering both download and SVN checkout. I did both this time but I think I might give up on the downloads because who out there, who understands that he/she needs to debug RAM usage, doesn't know how to use SVN?

Finally a little disclaimer: By writing about this here, preparing it on Google Code and writing a README.txt file I've now spent more time "managing" the project than I have on coding it. It's an early test release which hopefully will stir up some ideas for genuine important improvements. I had fun coding it as well since this is my first attempt with Flot which has been great to work with. You get very quick and powerful results. Lastly, I haven't tested this in anything but 32-bit Ubuntu Linux and Firefox.

Here is a sample report: 2008-05-30_16.47.32__3.8_minutes

29th of April

Releasing IssueTrackerProduct 0.9

Tonight I released an experimental version of the IssueTrackerProduct that is packed with new cool stuff. I call this an experimental release (but I run it on my production systems) because it's got so many new features.

During the course of preparing for this release and writing the news item I deployed the latest version to real.issuetrackerproduct.com and immediately noticed two bugs I to do with user names. So I immediately fixed those and prepared a new release minutes after. I expect to release another more stable version within a few weeks.

11th of April

What I like and dislike about Grok

Martijn Faasen is my hero. Not only is an absolutely brilliant coder he's also able talk so that mortals understand.

This is why I like Grok

What he's replying about is mainly the question "What does Grok give me that, say, django does not?"

And, this is why I dislike Grok

Yes, you clever people. It's the same link. For some reason all the great documentation goes into replies on the mailing list rather than into a concise web page with cookbook, book and styled and funny tutorials. Why is that? They've actually made it quite easy now to enter documentation on grok.zope.org with the new Plone site.

An equally important question is: Why don't I do something about it rather than to complain? Well, I've written one how-to at least. My other "excuse" is that I'm not yet an expert enough and hence writing good documentation takes a very long time.

I think there's an important philosophical and political issue at hand. The Grok community is filled with really clever people who are very senior in the web development industry who like using mailing lists and perhaps more importantly, don't need documentation since they can study source code and unit tests to answer their questions. I know this is a sensitive statement but I'll take my chances since it implies that these guys are smarter (or perhaps just more time on their hands).

My internal battle of which new web framework to put my energy into continues. Today (thanks to Martijn's post) Grok earned one more point.

9th of April

Mixing in new-style classes in Zope 2.7

Don't ask why I'm developing products for Zope 2.7 but I had to and I should have been more careful with these oldtimers.

I kept getting this error:

 TypeError:  expected 2 arguments, got 1

(notice the strange double space after the : colon) This is different from the standard python TypeError when you get the parameters wrong which looks like this TypeError: __init__() takes exactly 2 arguments (1 given).

The line it complained this happened looked like this:

 class MyTool(SimpleItem, UniqueObject, OtherClass):
    id = 'some_tool'
    meta_type = 'some meta type'
    def __init__(self, id='some_tool'):
        self.id = id  # <--- THIS WAS THE CULPRIT LINE APPARENTLY!!

I couldn't understand what the hell was wrong on that line! Clearly it wasn't a normal Python error. Here's the explaination: That OtherClass was a new-style class inheriting from object. It looked like this:

 class OtherClass(object):
    ...

When I changed that to:

 class OtherClass:
    ...

The whole thing started to work. Long lesson learnt, don't use new-style classes mixed in into Zope 2.7.

24th of March

Tip: Printer friendly pages with Page Templates in Zope

Since I've seen so many poor solutions to this problem I thought I'd share mine. Here's how I make printer friendly pages.

1. Add a method in your base class that looks like this:

  def getMainTemplate(self):
       """ return the suitable METAL header object """
       # assuming zpt/main_template.zpt
       template_obj = self.main_template

       # assuming the "first" line of main_template.zpt to
       # look like this:
       # <metal:block metal:define-macro="master">
       return template_obj.macros['master']

2. Change all your Page Templates to refer to a method rather than a macro directly so that pages like index_html.zpt start like this:

 <html metal:use-macro="here/getMainTemplate">

I've seen the hard coded way too many times where people do something like this <html metal:use-macro="here/main_template/macros/master"> which gives you no flexibility.

3. Now make a copy of main_template.zpt called print_main_template.zpt and the most important change is to make print.css load by default. Here's what it should look like this somewhere inside the <head> tag:

 <link rel="stylesheet" type="text/css" href="/screen.css" media="screen" />
 <link rel="stylesheet" type="text/css" href="/print.css" />

Note how the print.css link tag is now not conditional. Before in main_template.zpt it should have looked like this:

 <link rel="stylesheet" type="text/css" href="/print.css" media="print" /> 
 <link rel="stylesheet" type="text/css" href="/screen.css" media="screen" />

And note how the order is stacked just to be extra safe to weird browsers that don't understand the media condition.

As a last optional feature you should add is to add these lines at the bottom of the template 'print_main_template.zpt':

 <script type="text/javascript">
 window.print();
 </script>
 </body>
 </html>

Another tip is to add something like this to the footer because it becomes useful when you look at a printed copy:

 <div id="footer">
    Printed from <span tal:replace="here/absolute_url"></span> on 
    <span tal:replace="python:here.ZopeTime().strftime('%Y/%m/%d')"></span>

4. Now rewrite the method getMainTemplate() to become usefully intelligent:

   def getMainTemplate(self):
       """ return the suitable METAL header object """
       if self.REQUEST.get('print-version'):
           # assuming zpt/print_main_template.zpt
           template_obj = self.print_main_template
       else:
           # assuming zpt/main_template.zpt
           template_obj = self.main_template

       # assuming the "first" line of main_template.zpt to
       # look like this:
       # <metal:block metal:define-macro="master">
       return template_obj.macros['master']

5. Prepare the interface now for the printer friendly page. This can be done in two different ways. One way is to put a link in the footer or byline like this:

 <a href="?print-version=1">Print this page</a>

Or if you want to force a particular page to always be printer friendly, for example print_invoice.zpt then write it like this:

 <tal:item define="dummy python:request.set('print-version',1)"
           replace="nothing" 
  /><html metal:use-macro="here/getMainTemplate">

As a final point; how you solve your web design with screen.css and print.css varies. One way is to define multiple css files each suitable for individual things like this example shows:

 <link rel="stylesheet" type="text/css" href="/typography.css" /> 
 <link rel="stylesheet" type="text/css" href="/print.css" media="print" /> 
 <link rel="stylesheet" type="text/css" href="/screen.css" media="screen" />

An alternative solution is to don't expect print.css to stand on its own two legs but only be a supplement of the general css file. When doing this you're probably just going to want to override some things and hide some other things like this example from a 'print.css':

 body {
    width:100% !important
 }

 form#login, #navigation, .also-online {
   display:none
 }

To conclude

This gives you a robust framework for enabling printer friendly pages that are quite different from the main template and doing it like this means that you don't have add conditional hacks to your main template that displays certain things if in printer friendly mode or not.

Most importantly, this gives you the framework for adding other versions of main template. For example these:

  • mobile_main_template.zpt (guess what for)
  • minimal_main_template.zpt (for things like Help page popups)

A healthy and fair use of METAL macros is also key to asserting that you don't have to repeat yourself too much in the copies of main_template.pt.

Good luck!

14th of March

Mocking a Python standard library

Here's one of many things I've learnt today at PyCon. Inspired by code that Grig Gheorghiu showed in his slides on automated testing, you can monkey patch a standard library that your application is using in your unit tests to, in my case, mock a remote service without having to run a server. I've done lots of monkey-patching in Zope but then I've only been monkey patching individual methods or attributes of imported classes. This is very similar to that. Here's what my application does:

 from poplib import POP3
 class MyZopeApp(...):
    def check4mail(self, hostname, port, user, pwd):
        connection = POP3(hostname, port=port)
        ...download emails and process them...

Adjacent to this I have a unit/integration test that looks like this:

 class TestCase(ZopeTestCase):
    def test_check4mail(self):
        # monkey patch!
        # note that this imports a module, not a class
        from Products.IssueTrackerProduct import IssueTracker 
        FakePOP3.files = ('test1.email',)
        IssueTracker.POP3 = FakePOP3

        # now check what happens when check4mail() is run
        result = self.folder.tracker.check4mail()
        assert ...


>Read the whole text (299 more words)

6th of February

logrotating all my Zope event logs

I've installed a lot of Zope instances on my laptop since version 2.7.3 and out of curiosity and desperate need for more hard drive space I thought I'd log rotate them all with the standard Linux logrotate program.

Before doing the log rotate, the total size of all my event.log files came to about 290Mb! After running logrotate (twice of course to go from event.log.1 to event.log.2.gz) the total size become 20Mb. Not a huge significance in the world of gigabyte hard drives but at least something.

28th of October

DateIndex in Zope doesn't have indexed attributes

This took me a while to grok and perhaps by mentioning it here, it'll prevent other people from making the same mistake as I did and perhaps preventing myself from doing the same mistake again.

In the ZCatalog, when you set up indexes you can give them a name and an index attribute. If you omit the index attribute, it'll try to hook into the objects by the name of the index. For example, if you set the index to be title with no indexed attribute it'll fetch the title attribute of the objects it catalogs. But if you set the indexed attribute to be something like idx_getTitle you can do something like this in your class:

 def idx_getTitle(self):
    """ return title as we want it to be available in the ZCatalog """
    return re.sub('<*.?>','', self.title)

The same can not be done with indexes of type DateIndex.


>Read the whole text (262 more words)

15th of October

IssueTrackerProduct 0.8.0

Yesterday I released IssueTrackerProduct 0.8.0 which I'm quite happy about. It's been 10 months since the last release and although there are still some outstanding feature requests and non-urgent bugs I'm happy to get this out of the door.

To all those doing the upgrade, remember to press the Update Everything button!

14th of September

Nasty human error in Zope ZEO setup

Together with my colleague Jan we today tried to fix a problem with Zope ZEO server that wouldn't start. Very strange we thought since another ZEO server on the same machine was working fine. We compared zope.conf files and found nothing obvious. Here was the error you get with 'bin/runzeo':

 root@da-ovz-vm99182:/var/lib/zope-2.8.9/aragdb-zeo1# ./bin/runzeo 
 Error: error opening file //var/lib/zope-2.8.9/aragdb-zeo1/etc/zeo.conf: 
 [Errno ftp error] no host given

What?? ftp error??

Long story short, the error was this line in 'runzeo':

 CONFIG_FILE="/${INSTANCE_HOME}/etc/zeo.conf"

which should have been this:

 CONFIG_FILE="${INSTANCE_HOME}/etc/zeo.conf"

Not so easy to spot because the CONFIG_FILE line is something you rarely look closely at.

7th of September

Fun Python error message

I saw this today on the Zope mailing list:

 TypeError: open() takes at least 1946389116 arguments (4 given)

For anybody who's ever used Python you'll find this funny.

16th of August

rfc822() vs. rfc1123_date()

To set the Expires header in my web application I used to use the Zope DateTime function rfc822() which doesn't return the date in GMT format. Here's how I used it:

 >>> from DateTime import DateTime
 >>> hours = 5
 >>> then = DateTime() + hours/24.0
 >>> then.rfc822()
 'Thu, 16 Aug 2007 20:43:59 +0100'

Then I found out (from using YSlow) that it's better to use the GMT format (RFC 1123), and here's how to do that in Zope:

 >>> from App.Common import rfc1123_date
 >>> from time import time
 >>> rfc1123_date(time() + 3600*hours)
 'Thu, 16 Aug 2007 19:45:12 GMT'

(notice that even though my locale is here in London, because of the summer time an hour is added)


>Read the whole text (219 more words)

30th of May

Playing with filestream_iterator

There are several ways of serving static files from Zope. The simplest way is to just do something like this:

 size = os.stat(file_path)[stat.ST_SIZE]
 REQUEST.RESPONSE.setHeader('Content-Type','image/jpeg')
 REQUEST.RESPONSE.setHeader('Content-length',int(size))
 return open(file_path, 'rb').read()

The disadvantage with this is if the file is say >1Mb, the whole 1Mb needs to be loaded into RAM before it can be sent. This is surely garbage collected afterwards but if you serve many of these at the same time, there's obviously the risk that the RAM gets too full.

The alternative is to use filestream_iterator from the ZPublisher which is an iterator that sends out chunks of the file at a time. Note that to use the filestream_iterator you must set the Content-Length header first. Here's how to use it:

 from ZPublisher.Iterators import filestream_iterator
 ...
 size = os.stat(file_path)[stat.ST_SIZE]
 REQUEST.RESPONSE.setHeader('Content-Type','image/jpeg')
 REQUEST.RESPONSE.setHeader('Content-length',int(size))
 return filestream_iterator(file_path, 'rb')


>Read the whole text (202 more words)

21st of April

Guess my age with MOBi

Guess my age with MOBi This week we improved MOBi with a neat little feature that makes it possible to decide what the SMS response should be to inbound SMS on your own server.

To test this you need to be UK resident and willing to part with the 25p it costs to receive the response. Send peterbe is NN (where NN is a number, how old you think I am) to 83211 and await a result. I've set up the sub word "is *" to forward all inbound SMS to an address on www.peterbe.com and here on this server I run the following Python Script:

 ##parameters=sender, message
 ##
 guess = message.split()[-1]
 try:
     age = int(guess)
 except ValueError:
     return "That's not a number :-("

 if age > 27:
     return "That's not it! It's less than that. Try again"
 elif age < 27:
     return "That's not it! It's more than that. Try again"
 return "Wow! You know my age!!"

16th of March

Zope Image to filesystem image

Today I bumped into a nasty challenge when trying to copy a Zope image from ZODB to the filesystem. The reason why it wasn't easy was that Zope splits the file up into chunks if it's upload size is more than 65Kb (2^16 bytes) which I guess it does for being able to "stream" the file back into the browser when viewing the image instead of sending one large blob.

So, this didn't work:

 >>> imgobj.meta_type
 Image
 >>> open(temp_file_path,'wb').write(imgobj.data)

...if the data is big because sometimes imgobj.data is a string (the binary data) and sometimes is a ImplicitAcquirerWrapper that you have to iterate over.


>Read the whole text (233 more words)

6th of March

Lukasz Lakomy's new website

Lukasz Lakomy is the latest fulltime addition to Fry-IT, my company. Lukasz is a kickass Plone and Zope developer and has just launched his own little website which is done in Plone.

The content is still a bit on the light side but he's already managed to start hosting his wonderful little ZPTDebugger which has now reached a 1.0 release. This product wasn't created because it was possible but because he actually needed it. Applications that come from that kind of motivation always end up being much more useful.

Keep up the good work colleague!

30th of January

Gzip and Slimmer optimization anecdote

I've wanted to Gzip the static content (CSS & Javascript) on my sites for a long time but never found a good enough solution. mod_gzip is out of the question because as far as I've understood it it does a compression on the fly, every time you request the file.

Other solutions have disappointed me because enabling gzip compression has been for all content. I don't want my HTML files gzipped because they're rendered on the fly based on business logic plus by compressing the HTML files. Long story short my yet-to-be-released app now serves the following files from Zope but are only compressed and whitespace slimmed once per server restart:

 FILE               ORIG SIZE  NEW SIZE   REDUCTION
 screen.css             15224      2738   556%
 print.css               2633       885   298%
 jquery-latest.js*      57712     18130   318%
 jquery-latest.pack.js  20461     10513   195%
 common.js               3803      1131   336%
 complete-expense.js    18184      2847   639%
 Total                 118017     36244   326%

 * only used in debug mode


>Read the whole text (314 more words)

29th of November

MUnderscorePatch - tired of typing manage_main?

I often use the Zope management interface but not to do much ZMI like stuff. Just simple things like creating new base objects, changing properties or testing a change to a template. For some reason I seem to lack the ability to spell the word manage_main. I can't remember how many times I've accidently typed manage-main , mange_main, manag_main, manage)_main etc.

There's got to be an end to that! Hence the MUnderscorePatch monkey patch. Now, all I have to do is to type in m_ at the end of the URL and it becomes the same as manage_main. Ie. I go to http://localhost:8080/ then I append m_ so that it becomes http://localhost:8080/m_ and it redirects automatically to http://localhost:8080/manage_main

Silly but actually quite useful for me. Want to download it too?

26th of October

Sending HTML emails in Zope

Here are a few lessons learnt when sending HTML emails in Zope with unicode data. I'm still a beginner with this stuff and still probably have a lot to learn. What I've achived now works but might break with different encodings or on different systems so don't assume that these patterns will work in your setup.

To the send the email I'm using the default MailHost that comes with Zope 2.8.5. I call it's send() passing the subject line a second time because the subject line is already in the body string.

The most valuable piece of magic to learn and remember from this is that when you construct the multi-part message you have to attach the plain text before the html text. Thank you Lukasz Lakomy for that tip!


>Read the whole text (171 more words)

7th of October

Zope 3 training blog

Just wanted to let you know that I've written a blog on my company's website about the Zope 3 training course I've attended in Denmark.

21st of September

Comparing Ruby and PHP

I was reading Of snakes and rubies; Or why I chose Python over Ruby which is an article comparing Python and Ruby. The blog article has loads of comments. This one I liked in particular

"For people writing good production code in PHP, I'm not trying to pile on - I know you get disrespected enough. It's possible to write well-structured sites on PHP, and lots of you do. It's just that in PHP, anyone can learn how to create spaghetti code in 10 minutes, but you have to learn a decent-sized body of knowledge to gain the ability to separate presentation from logic. By contrast, when novices get started in Rails, they're using good structure by default. They have to make an effort to screw it up."


>Read the whole text (298 more words)

1st of June

TinyMCE + Zope = ZTinyMCE

ZTinyMCE in action This is my first release of ZTinyMCE. ZTinyMCE makes it easy to use TinyMCE, the best Open Source WYSIWYG editor in my opinion.

We at Fry-IT use this a lot for our websites as shown in the screenshots. The ZTinyMCE product works only with Zope2. Once you have it installed and you have created a ZTinyMCE instance in your root plus a ZTinyMCE Configuration object called "tinymce.conf" all you have to do to convert a plain page of HTML with textareas to a plain page of HTML with WYSIWYG textareas is this:

 <script tal:replace="structure here/tinymce.conf">
 </script>

2nd of February

Setting security declarations to Zope classes

If you're into Zope python product stuff, read on, otherwise don't bother.

Thanks to Brian Lloyd (Zope corp) and Florent Guillaume (Nuxeo) I now know how to set security declarations on a class outside the class. It doesn't work like a normal python class (new or old style) which was a bit of a surprise. This is how you do it in a "normal" python class:

 class _Z:
   def __init__(self):
       self.z = "Z"
   def declareProtected(self, *a,**k):
       print "++declare something+"

 def foo():
   print "I'm being called"
   return _Z()

 class A:
   security=foo()
   def __init__(self):
       pass
 A.security.declareProtected("foo")


>Read the whole text (299 more words)

16th of December

IssueTrackerProduct 0.7 released

Email replies and AJAX is the highlight of the latest IssueTrackerProduct release.

If you set up your POP3 for email replies you can simply hit reply on the notifications that goes out and continue an issue discussing without leaving the comfort of Outlook/Thunderbird/Gmail/Hotmail.

Another underestimated important feature is the automatically refreshing issue where the content is periodically refreshed (if the content changes) whilst you're on the page so that you know you're always looking at the very latest copy before you take any action.

For the people who've dared to use the CVS, they've probably already seen the new keyboard shortcuts that is inspired by Gmail. It's very addictive to not have to scroll up and down to reach the navigation or to be able to quickly jump to another issue without having to click on List Issue.

And for the Gettings Things Done people you might like how you now can get a list of "You next action issues" on the Home page. This is a clever list that attempts to figure out what you need to do next in that order.

19th of November

Major performance fix on file searches

A week ago I ran some ad hoc benchmarks on various suspect functions in the IssueTrackerProduct and came to a clear and simple conclusion: searching is the bottleneck and within the search it's the searching for file attachments that take all of the time.

If you're interested and open minded, here's the results of that benchmark This sparked some thoughts. First I wrote a filename splitter which isn't rocket science but I'm proud to say that it's use is brilliant. Before, the find-by-file function in the IssueTrackerProduct used a plain old find() test like this:

 filename.find('foo') > -1

This is very fast but not very intelligent. For example it'll with match on foobar.txt and plainfooter.gif. So, what I did instead was to create a KeywordIndex and index all the splitted filenames in that index.


>Read the whole text (192 more words)

17th of November

Automatically refreshing issue

Just wanted to make sure you all know about my latest invention :) It's an AJAX implementation on the IssueTrackerProduct that updates an issue page you might be looking at if it's changed on the server. This will become very useful if there are several people working on the tracker and you don't want to make sure you're reading the very latest when you post your followup.

This accomplished by having a periodic Javascript (about 30 seconds at the time of writing) that constantly does a timestamp check against the server. If the timestamp changes, the issue you're viewing will most likely have changed and that's where AJAX kicks in. The AJAX script reloads certain parts of the page and also changes the title of the page (observe that in the screencast).

One thing I just noticed about the screencast is that the debug-mode was switched on which means that there's lot's of geeky stuff at the bottom of the Internet Explorer window. Oops!

 

Older entriesOrder entries