Kwissle

My real-time quiz battle game Kwissle.com

Crosstips.org

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

Kung Fu

Fujian White Crane Kung Fu

Photos

Photoalbum, both old and new.

Twitter

Follow me on Twitter

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 : Pretty cool app! BTW I'm working now on my own first web app: http://www.fi...

My first iPhone web app - Crosstips iPhone interfa

by Jonathan: I had a similar issue with setAttribute not working on IE. I put it in a t...

setAttribute('style', ...) workaround for IE

by terrence: The simpsons are from Ohio. Check the Halloween special #5 or #6. The one w...

Ask Yahoo "What state do the Simpsons live in?"

by terrence: The simpsons are from Ohio. In the Halloween episode where the giant ads c...

Ask Yahoo "What state do the Simpsons live in?"

by Ashraf at Akbar: Hi Thanks for help...

Lost my mobile phone

by selevistar: verey good...

fcgi vs. gunicorn vs. uWSGI

by scott: the simpsons live in florida because in the simpson hit and run buy marges ...

Ask Yahoo "What state do the Simpsons live in?"

by Peter Bengtsson: It's a Tornado thing. It's awesome because it's an alternative to using cal...

Persistent caching with fire-and-forget updates

by Paul Winkler: Can you explain the use of "yield" in that code, or link to something that ...

Persistent caching with fire-and-forget updates

by mathanraj: Oh thats work great... thankssssssss........

setAttribute('style', ...) workaround for IE

Old entries


July, 2011
A blog comment spam solution: Retalition!
A taste of the Django on inside Mozilla, Sheriffs Duty
Comparing Google Closure with UglifyJS
Slides about Kwissle from yesterdays London Python Dojo

June, 2011
Chinese tea sampler pack now on sale
Optimization story involving something silly I call "dict+"
Launching Kwissle.com
Google teething problems still with duplicated content
Test static resources in Django tests

2011
2010
2009
2008
2007
2006
2005
2004
2003

 

13th of December

PythonTornado

Persistent caching with fire-and-forget updates

I just recently landed some patches on toocool that implements and interesting pattern that is seen more and more these days. I call it: Persistent caching with fire-and-forget updates

Basically, the implementation is this: You issue a request that requires information about a Twitter user: E.g. http://toocoolfor.me/following/chucknorris/vs/peterbe The app looks into its MongoDB for information about the tweeter and if it can't find this user it goes onto the Twitter REST API and looks it up and saves the result in MongoDB. The next time the same information is requested, and the data is available in the MongoDB it instead checks if the modify_date or more than an hour and if so, it sends a job to the message queue (Celery with Redis in my case) to perform an update on this tweeter.

You can basically see the code here but just to reiterate and abbreviate, it looks like this:

 tweeter = self.db.Tweeter.find_one({'username': username})
 if not tweeter:
    result = yield tornado.gen.Task(...)
    if result:
        tweeter = self.save_tweeter_user(result)
    else:
        # deal with the error!
 elif age(tweeter['modify_date']) > 3600:
    tasks.refresh_user_info.delay(username, ...)
 # render the template!

What the client gets, i.e. the user using the site, is it that apart from the very first time that URL is request is instant results but data is being maintained and refreshed.

This pattern works great for data that doesn't have to be up-to-date to the second but that still needs a way to cache invalidate and re-fetch. This works because my limit of 1 hour is quite arbitrary. An alternative implementation would be something like this:

 tweeter = self.db.Tweeter.find_one({'username': username})
 if not tweeter or (tweeter and age(tweeter) > 3600 * 24 * 7):
     # re-fetch from Twitter REST API
 elif age(tweeter) > 3600:
     # fire-and-forget update

That way you don't suffer from persistently cached data that is too old.

6th of December

Django

Cryptic errors when using django-nose

After about 3 days of debugging using pdb, print and writing to a log file I've almost finally solve my bizarre errors I was getting when running a whole test suite. The error that it lead to was that Django refused to re-register models to the admin and the errors looked something like this:

  ...
  File "/Users/peterbe/dev/MOZILLA/PTO/pto/urls.py", line 6, in <module>
    admin.autodiscover()
  File "/Users/peterbe/dev/MOZILLA/PTO/pto/vendor/src/django/django/contrib/admin/__init__.py", line 26, in autodiscover
    import_module('%s.admin' % app)
  File "/Users/peterbe/dev/MOZILLA/PTO/pto/vendor/src/django/django/utils/importlib.py", line 35, in import_module
    __import__(name)
  File "/Users/peterbe/dev/MOZILLA/PTO/pto/apps/users/admin.py", line 30, in <module>
    admin.site.register(UserProfile, UserProfileAdmin)
  File "/Users/peterbe/dev/MOZILLA/PTO/pto/vendor/src/django/django/contrib/admin/sites.py", line 85, in register
    raise AlreadyRegistered('The model %s is already registered' % model.__name__)
 AlreadyRegistered: The model UserProfile is already registered

Turns out to be independent of which Django project I ran and it was something no one else was able to reproduce on any machine with the exact same code.

After 2 days I found that there's a difference between a successful run and a failing run was how I specified (to nose) which module to load:

 ./manage.py test users  # fails!
 ./manage.py test users.test  # works!

In both cases it finds the same tests. So it would either fail 10 times or work 10 times. Hmmm...

The bridging between nose and Django is done by awesome django-nose developed here at Mozilla by Django extraordinaire Jeff Balogh and it's a non-trivial piece of code as it depends on some really smart importing tricks and stuff which I haven't even begun to understand.

However, after so many trial and errors I finally discovered that the solution (for me) was to delete the ~/.noserc file. What's strange is that all it contained was:

 [nosetests]
 with-doctest=1

I might never actually find out what went wrong. Ultimately I think a reason things went wrong was because it incorrectly populated sys.modules with excessive keys that would cause double imports of urls.py which in turn runs admin.autodiscover() but incorrectly does so twice.

Sorry for the rambling. And sorry for not actually finding the real bug. I did spent 2-3 days debugging this non-stop and hopefully some other poor frustrated person is going to see this and also look into the ~/.noserc for ways to fix it maybe.

2nd of December

Python

Python file with closing automatically

Perhaps someone who knows more about the internals of python and the recent changes in 2.6 and 2.7 can explain this question that came up today in a code review.

I suggest using with instead of try: ... finally: to close a file that was written to. Instead of this:

 dest = file('foo', 'w')
 try:
    dest.write('stuff')
 finally:
    dest.close()
 print open('foo').read()  # will print 'stuff'

We can use this:

 with file('foo', 'w') as dest: 
     dest.write('stuff')
 print open('foo').read()  # will print 'stuff'

Why does that work? I'm guessing it's because the file() instance object has a built in __exit__ method. Is that right?

That means I don't need to use contextlib.closing(thing) right?

For example, suppose you have this class:

 class Farm(object):
    def __enter__(self):
        print "Entering"
        return self
    def __exit__(self, err_type, err_val, err_tb):
        print "Exiting", err_type
        self.close()
    def close(self):
        print "Closing"

 with Farm() as farm:
    pass
 # this will print:
 #   Entering
 #   Exiting None
 #   Closing

Another way to achieve the same specific result would be to use the closing() decrorator:

 class Farm(object):
    def close(self):
        print "Closing"

 from contextlib import closing
 with closing(Farm()) as farm:
    pass
 # this will print:
 #   Closing

So the closing() decorator "steals" the __enter__ and __exit__. This last one can be handy if you do this:

 from contextlib import closing
 with closing(Farm()) as farm:
    raise ValueError

 # this will print
 #  Closing
 #  Traceback (most recent call last):
 #   File "dummy.py", line 16, in <module>
 #     raise ValueError
 #  ValueError

This is turning into my own loud thinking and I think I get it now. contextlib.closing() basically makes it possible to do what I did there with the __enter__ and __exit__ and it seems the file() built-in has a exit handler that takes care of the closing already so you don't have to do it with any extra decorators.

21st of November

TornadoMozilla

Integrate BrowserID in a Tornado web app

Integrate BrowserID in a Tornado web app BrowserID is a new single sign-on initiative lead by Mozilla that takes a very refreshing approach to single sign-on. It's basically like OpenID except better and similar to the OAuth solutions from Google, Twitter, Facebook, etc but without being tied to those closed third-parties.

At the moment, BrowserID is ready for production (I have it on Kwissle) but the getting started docs is still something that is under active development (I'm actually contributing to this).

Anyway, I thought I'd share how to integrate it with Tornado

First, you need to do the client-side of things. I use jQuery but that's not a requirement to be able to use BrowserID. Also, there are different "patterns" to do login. Either you have a header that either says "Sign in"/"Hi Your Username". Or you can have a dedicated page (e.g. mysite.com/login/). Let's, for simplicity sake, pretend we build a dedicated page to log in. First, add the necessary HTML:

 <a href="#" id="browserid" title="Sign-in with BrowserID">
   <img src="/images/sign_in_blue.png" alt="Sign in">
 </a>
 <script src="https://browserid.org/include.js" async></script>

Next you need the Javascript in place so that clicking on the link above will open the BrowserID pop-up:

 function loggedIn(response) {
   location.href = response.next_url;
   /* alternatively you could do something like this instead:
        $('#header .loggedin').show().text('Hi ' + response.first_name);
     ...or something like that */
 }

 function gotVerifiedEmail(assertion) {
  // got an assertion, now send it up to the server for verification
  if (assertion !== null) {
    $.ajax({
      type: 'POST',
      url: '/auth/login/browserid/',
      data: { assertion: assertion },
      success: function(res, status, xhr) {
        if (res === null) {}//loggedOut();
        else loggedIn(res);
      },
      error: function(res, status, xhr) {
        alert("login failure" + res);
      }
    });
  }
  else {
    //loggedOut();
  }
 }

 $(function() {
   $('#browserid').click(function() {
     navigator.id.getVerifiedEmail(gotVerifiedEmail);
     return false;
   });
 });

Next up is the server-side part of BrowserID. Your job is to take the assertion that is given to you by the AJAX POST and trade that with https://browserid.org for an email address:

 import urllib
 import tornado.web
 import tornado.escape 
 import tornado.httpclient
 ...

 @route('/auth/login/browserid/')  # ...or whatever you use
 class BrowserIDAuthLoginHandler(tornado.web.RequestHandler):

    def check_xsrf_cookie(self):  # more about this later
        pass

    @tornado.web.asynchronous
    def post(self):
        assertion = self.get_argument('assertion')
        http_client = tornado.httpclient.AsyncHTTPClient()
        domain = self.request.host
        url = 'https://browserid.org/verify'
        data = {
          'assertion': assertion,
          'audience': domain,
        }
        response = http_client.fetch(
          url,
          method='POST',
          body=urllib.urlencode(data),
          callback=self.async_callback(self._on_response)
        )

    def _on_response(self, response):
        struct = tornado.escape.json_decode(response.body)
        if struct['status'] != 'okay':
            raise tornado.web.HTTPError(400, "Failed assertion test")
        email = struct['email']
        self.set_secure_cookie('user', email,
                                   expires_days=1)
        self.set_header("Content-Type", "application/json; charset=UTF-8")
        response = {'next_url': '/'}
        self.write(tornado.escape.json_encode(response))
        self.finish()

Now that should get you up and running. There's of couse a tonne of things that can be improved. Number one thing to improve is to use XSRF on the AJAX POST. The simplest way to do that would be to somehow dump the XSRF token generated into your page and include it in the AJAX POST. Perhaps something like this:

 <script>
 var _xsrf = '{{ xsrf_token }}';
 ...
 function gotVerifiedEmail(assertion) {
  // got an assertion, now send it up to the server for verification
  if (assertion !== null) {  
     $.ajax({
      type: 'POST',
      url: '/auth/login/browserid/',
      data: { assertion: assertion, _xsrf: _xsrf },
 ... 
 </script>

Another thing that could obviously do with a re-write is the way users are handled server-side. In the example above I just set the asserted user's email address in a secure cookie. More realistically, you'll have a database of users who you match by email address but instead store their database ID in a cookie or something like that.

What's so neat about solutions such as OpenID, BrowserID, etc. is that you can combine two things in one process: Sign-in and Registration. In your app, all you need to do is a simple if statement in the code like this:

 user = self.db.User.find_by_email(email) 
 if not user:
     user = self.db.User()
     user.email = email
     user.save()
 self.set_secure_cookie('user', str(user.id))

Hopefully that'll encourage a couple of more Tornadonauts to give BrowserID a try.

18th of November

Python

Trivial but powerful tips for nosetests

I'm clearly still a nosetests beginner because it was only today that I figured out how to set certain plugins to always be on.

First of all you might like these plugins too:

 $ pip install rudolf
 $ pip install disabledoc

Docs: rudolf and disabledoc

To get these gorgeous little tricks into every run of nosetests edit the file ~/.noserc and add the following:

 [nosetests]
 with-disable-docstring=1
 with-color=1

That should make your life a little easier.

UPDATE:

I've since managed to shoot myself in both legs with messing around with nosetests plugins because I heavily rely on django-nose in Django. Long story short: be careful if you get strange import related errors!

13th of October

Web developmentJavaScript

Going real simple on HTML5 audio

DoneCal users are to 80+% Chrome and Firefox users. Both Firefox and Chrome support the HTML <audio> element without any weird plugins and they both support the Ogg Vorbis (.ogg) file format. change log here

So, I used use the rather enterprisey plugin called SoundManager2 which attempts to abstract away all hacks into one single API. It uses a mix of browser sniffing, HTML5 and Flash. Although very promising, it is quite cumbersome. It doesn't work flawlessly despite their hard efforts. Unfortunately, using it also means a 30kb (optimized) Javascript file and a 3kb .swf file (if needed). So, instead of worrying about my very few Internet Explorer users I decided to go really dumb and simple on this.

The solution basically looks like this:

 // somewhere.js
 var SOUND_URLS = {
   foo: 'path/to/foo.ogg',
   egg: 'path/to/egg.ogg'
 };

 // play-sounds.js

 /* Call to create and partially download the audo element.
  * You can all this as much as you like. */

 function preload_sound(key) {
  var id = 'sound-' + key;
  if (!document.getElementById(id)) {
    if (!SOUND_URLS[key]) {
      throw "Sound for '" + key + "' not defined";
    } else if (SOUND_URLS[key].search(/\.ogg/i) == -1) {
      throw "Sound for '" + key + "' must be .ogg URL";
    }
    var a = document.createElement('audio');
    a.setAttribute('id', id);
    a.setAttribute('src', SOUND_URLS[key]);
    document.body.appendChild(a);
  }
  return id;
 }

 function play_sound(key) {
   document.getElementById(preload_sound(key)).play();
 }

 // elsewhere.js
 $.lightbox.open({
    onComplete: function() {
       preload_sound('foo');
    }
 });
 $('#lightbox button').click(function() {
    play_sound('foo');
 });

Basically, only Firefox, Chrome and Opera support .ogg but it's a good and open source encoding so I don't mind being a bit of an asshole about it. This little script could be slightly extended with some browser sniffing to work with Safari people but right now it doesn't feel like it's worth the effort.

This make me happy and I feel lean and light. A good feeling!

8th of October

Web development

New feature on Too Cool For Me: Everyone I follow

New feature on Too Cool For Me: Everyone I follow I've added a new feature to Too Cool For Me that lists all the users that you follow and splits them up into "Follows me" and "Too cool for me".

To try it you have to authenticate with Twitter (READ ONLY mode) then go to toocoolfor.me/everyone

This means you can use Too Cool For Me without having to use the Bookmarklet.

27th of September

Misc. links

Cateechee golf pictures

Cateechee golf pictures My friend Josh Ziff has uploaded pictures from the little golf tournament (we played scrambles) at Cateechee in Hartwell, Georgia a couple of weeks ago.

Check out the pictures here

25th of September

Tornado

Too Cool For Me?

Too Cool For Me? Too Cool For Me? is a fun little side-project I've been working on. It's all about and only for Twitter. You login, then install a bookmarklet then when browsing twitter you can see who follows you and who is too cool for you.

For me it's a chance to try some new tech and at the same time scratch an itch I had. The results can be quite funny but also sad too when you realise that someone uncool isn't following you even though you follow him/her.

The code is open source and available on Github and at least it might help people see how to do a web app in Tornado using MongoDB and asynchronous requests to the Twitter API

23rd of September

Tornado

Goodies from tornado-utils - part 3: send_mail

This is Part 3 in a series of blogs about various bits and pieces in the tornado-utils package. Part 1 is here and part 2 is here

send_mail

Code is here

First of all, I should say: I didn't write much of this code. It's copied from Django and modified to work in any Tornado app. It hinges on the same idea as Django that you have to specify what backend you want to use. A backend is an importable string pointing to a class that has the send_messages method.

To begin, here's a sample use case inside a request handler:

 class ContactUs(tornado.web.RequestHandler):

    def post(self):
        msg = self.get_argument('msg')
        # NB: you might want to set this once and for all in something 
        # like self.application.settings['email_backend']
        backend = 'tornado_utils.send_mail.backends.smtp.EmailBackend'
        send_email(backend,
                   "New contact form entry",
                   msg + '\n\n--\nFrom our contact form\n',
                   'noreply@example.com',
                   ['webmaster@example.com'],
                   )
        self.write("Thanks!")

The problem is that SMTP is slow. Even though, in human terms, it's fast, it's still too slow for a non-blocking server that Tornado is. Taking 1-2 seconds to send a message over SMTP means it's blocking every other request to Tornado for 1-2 seconds. The solution is instead save the message on disk in pickled form and use a cron job to pick up the messages and send them by SMTP instead, outside the Tornado process. First do this re-write:

    ... 
    def post(self):
        msg = self.get_argument('msg')
-       backend = 'tornado_utils.send_mail.backends.smtp.EmailBackend'
+       backend = 'tornado_utils.send_mail.backends.pickle.EmailBackend'
    ...

Now, write a cron job script that looks something like this:

 # send_pickled_messages.py
 DRY_RUN = False

 def main():
    from tornado_utils.send_mail import config
    filenames = glob(os.path.join(config.PICKLE_LOCATION, '*.pickle'))
    filenames.sort()
    if not filenames:
        return

    from tornado_utils.send_mail import backends
    import cPickle

    if DRY_RUN:
        EmailBackend = backends.console.EmailBackend
    else:
        EmailBackend = backends.smtp.EmailBackend
    max_count = 10
    filenames = filenames[:max_count]
    messages = [cPickle.load(open(x, 'rb')) for x in filenames]
    backend = EmailBackend()
    backend.send_messages(messages)
    if not DRY_RUN:
        for filename in filenames:
            os.remove(filename)

That code just above is butchered from a more comprehensive script I have but you get the idea. Writing to a pickle file is so fast it's in the lower milliseconds region. However, it depends on disk IO so if you need more speed, write a simple backend that writes instead of saving pickles on disk, make it write to a fast in-memory database like Redis or Memcache.

The code isn't new and it's been battle tested but it's only really been battle tested in the way that my apps use it. So you might stumble across bugs if you use it in a way I haven't tested. However, the code is Open Source and happily available for you to help out and improve.

21st of September

Tornado

Goodies from tornado-utils - part 2: tornado_static

This is Part 2 in a series of blogs about various bits and pieces in the tornado-utils package. Part 1 is here

tornado_static

Code is here

This code takes care of two things: 1) optimizing your static resources and 2) bundling and serving them with unique names so you can cache aggressively.

The trick is to make your development environment such that there's no need to do anything when in "debug mode" but when in "production mode" it needs to be perfect. Which files (e.g. jquery.js or style.css) you use and bundle is up to you and it's something you control from the templates in your Tornado app. Not a config setting because, almost always, which resources (aka. assets) you need is known and relevant only to the templates where you're working.

Using UI modules in Tornado requires a bit of Tornado-fu but here's one example and here is another (untested) example:

 # app.py

 import tornado.web
 from tornado_utils.tornado_static import (
   StaticURL, Static, PlainStaticURL, PlainStatic) 

 class Application(tornado.web.Application):
    def __init__(self):
        ui_modules = {}
        if options.debug:
            ui_modules['Static'] = PlainStatic
            ui_modules['StaticURL'] = PlainStaticURL
        else:
            ui_modules['Static'] = Static
            ui_modules['StaticURL'] = StaticURL

        app_settings = dict(
            template_path="templates",
            static_path="static",
            ui_modules=ui_modules,
            debug=options.debug,
            UGLIFYJS_LOCATION='~/bin/uglifyjs',
            CLOSURE_LOCATION="static/compiler.jar",
            YUI_LOCATION="static/yuicompressor-2.4.2.jar",
            cdn_prefix="cdn.mycloud.com",
        )

        handlers = [
          (r"/", HomeHandler),
          (r"/entry/([0-9]+)", EntryHandler),
        ] 
        super(Application, self).__init__(handlers, **app_settings)

 def main(): # pragma: no cover
    tornado.options.parse_command_line()
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

 if __name__ == "__main__":
    main()

Note! If you're looking to optimize your static resources in a Tornado app you probably already have a "framework" for setting up UI modules into your app. The above code is just to wet your appetite and to show how easy it is to set up. The real magic starts to happen in the template code. Here's a sample implementation:

 <!doctype html>
 <html>
    <head>
        <title>{% block title %}{{ page_title }}{% end %}</title>
        <meta charset="utf-8">
        {% module Static("css/ext/jquery.gritter.css", "css/style.css") %}
    </head>
    <body>
       <header>...</header>
    {% block content %}
    {% end %}
    {% module Static("js/ext/head.load.min.js") %}
    <script>
    var POST_LOAD = '{% module StaticURL("js/dojo.js", "js/dojo.async.js") %}';
    </script>
    </body>
 </html>

What you get when run is a template that looks like this:

 <!doctype html>
 <html>
    <head>
        <title>My title</title>
        <meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="//cdn.mycloud.com/combined/jquery.gritter.css.style.1313206609.css">
    </head>
    ...

(Note that this will create a whitespace optimized filed called "jquery.gritter.css.style.1313206609.css" in "/tmp/combined")

Have a play and see if it makes sense in your app. I do believe this can do with some Open Source love but so far it works great for me on Kwissle, DoneCal and TooCoolFor.Me

19th of September

Tornado

Goodies from tornado-utils - part 1: TestClient

This is Part 1 in a series of blogs about various bits and pieces in the tornado-utils package.

tornado-utils is the result of me working on numerous web apps in Tornado that after too much copy-and-paste from project to project eventually became a repo standing on its own two legs.

TestClient

Code is here

This makes it possible to write integration tests that executes GET and POST requests on your app similarly to how Django does it. Example implementation:

 # tests/base.py

 from tornado_utils.http_test_client import TestClient, HTTPClientMixin
 from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase

 class BaseAsyncTestCase(AsyncHTTPTestCase, LogTrapTestCase):
    pass 

 class BaseHTTPTestCase(BaseAsyncTestCase, HTTPClientMixin):

    def setUp(self):
        super(BaseHTTPTestCase, self).setUp()
        self.client = TestClient(self)

 # tests/test_handlers.py

 from .base import BaseHTTPTestCase

 class HandlersTestCase(BaseHTTPTestCase):

    def setUp(self):
        super(HandlersTestCase, self).setUp()
        self._create_user('peterbe', 'secret')  # use your imagination

    def test_homepage(self):
        response = self.client.get('/')
        self.assertEqual(response.code, 200)
        self.assertTrue('stranger' in response.body)

        data = {'username': 'peterbe', 'password': 'secret'}
        response = self.client.post('/login/', data)
        self.assertEqual(response.code, 302)

        response = self.client.get('/')
        self.assertEqual(response.code, 200)
        self.assertTrue('stranger' not in response.body)
        self.assertTrue('hi peterbe' in response.body)

You can see a sample implementation of this here

Note that this was one of the first pieces of test code I wrote in my very first Tornado app and it's not unlikely that some assumptions and approaches are naive or even wrong but what we have here works and it makes the test code very easy to read. All it basically does is wraps the http_client.fetch(...) call and also maintains a bucket of cookies

I hope it can be useful to someone new to writing tests in Tornado.

2nd of September

Wondering

Reciprocal lesson about gender perspectives

Picked this up on a food blog in one of the comments; a female commenter wrote:

"Guys went for me all the time in my late teens and early twenties. I’m no great beauty, but they did anyway. I was slender.
I did not remain slender. No one looks at me now.
I'm the same person."

Err! Wrong! You're not the same person! If we assume that this "slender" versus "not slender" is the differentiating factor and ignore age for now then with beauty changes the person.

It's such a common misconception sometimes heard from women that the personality is one factor of attraction and that external beauty is another. In the opposite, women do play down the external beauty factor more when considering men's total attraction value. Take heed women, your beauty factor matters more than men's. No news there. The mistake here is to think the opposite sex thinks like you do.

So what's the reciprocal lesson in this? For a man. Quite simply: don't think that women think like men. Or in plain English, just because you're a stud, don't think that you can be an ass.

22nd of August

JavaScript

Title - a javascript snippet to control the document title

This is a piece of Javascript code I use on Kwissle to make the document title change temporarily. Other people might find it useful too.

Code looks like this:

 var Title = (function() {
  var current_title = document.title
    , timer;

  return {
     showTemporarily: function (msg, msec) {
       msec = typeof(msec) !== 'undefined' ? msec : 3000;
       if (msec < 100) msec *= 1000;
       if (timer) {
         clearTimeout(timer);
       }
       document.title = msg;
       timer = setTimeout(function() {
         document.title = current_title;
       }, msec);
     }
  }
 })();

Demo here

1st of August

Django

EmailInput HTML5 friendly for Django

Suppose you have a Django app with a login where people can only log in with their email address. Then use this widget on your login form:

 ## The input widget class
 class EmailInput(forms.widgets.Input):
    input_type = 'email'

    def render(self, name, value, attrs=None):
        if attrs is None:
            attrs = {}
        attrs.update(dict(autocorrect='off',
                          autocapitalize='off',
                          spellcheck='false'))
        return super(EmailInput, self).render(name, value, attrs=attrs)

 ## Example usage
 class AuthenticationForm(django.contrib.auth.forms.AuthenticationForm):
    """override the authentication form because we use the email address as the
    key to authentication."""

    # allows for using email to log in
    username = forms.CharField(label="Username", max_length=75,
                               widget=EmailInput())
    rememberme = forms.BooleanField(label="Remember me", required=False)

EmailInput HTML5 friendly for Django This input field does some cool stuff in the browser such as automatic validation in the browser as seen in this screenshot here.

More importantly it fixes a very annoying problem when surfing on a smartphone or a tablet like the iPad. As I'm about to type "someusername@mozilla.com" it first wants to start capitalized and which might fail the login. Also if the email address contains a word that it wants to correct like ("mozilla" -> "Mozilla") you have to click the little correct tooltip to tell the input is correct in verbatim.

Note to Djangonauts who want to use this and have a dual authentication backend that takes both usernames and email addresses, this form will make it impossible to log in as something called "admin" for example.

 

Older entries Order entries