<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xml:base="http://www.whijo.net" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
 <title></title>
 <link>http://www.whijo.net/comments/feed</link>
 <description>Display recent comments block</description>
 <language>en</language>
<item>
 <title>Mambo developers smoke crack, sometimes</title>
 <link>http://www.whijo.net/blog/brad/2005/06/30/mambo-developers-smoke-crack-sometimes.html</link>
 <description>RUCUS import: geeking,www
&lt;p&gt;After using &lt;a href=&quot;http://www.mamboserver.com&quot; class=&quot;external&quot;&gt;mambo&lt;/a&gt; for awhile it is clear that the mambo developers either don&#039;t have the time to, or don&#039;t really understand the power that can be gotten out of PHP, html, css, etc. There is a lot of instances where there is heaps of code to do something, and it could be simplified and shortened by just catering for a proper base case, and using things like the .= operator (for strings, for e.g.). The com_rss module is also on crack a bit, but i have hacked it a bit so that it is not as barmy, and now produces a decent feed for the &lt;a href=&quot;http://www.go-opensource.org&quot; class=&quot;external&quot;&gt;GOS&lt;/a&gt; site. It just annoys me that I have to dive into core source code to change things that should be exposed in the stylesheet, etc.&lt;/p&gt;
&lt;p&gt;That said, there is a lot of stuff that is pretty good, and to be honest &quot;fixing&quot; things usually is pretty straightforward, the code is not terrifically convoluted. It seems like the code and interface are undergoing major rennovations anyways, so it is getting there, for now there are a lot of things that have taken much hacking to set straight. And the html it produces is still pretty hideous. Getting there slowly though i guess.&lt;/p&gt;</description>
 <comments>http://www.whijo.net/blog/brad/2005/06/30/mambo-developers-smoke-crack-sometimes.html#comments</comments>
 <pubDate>Thu, 30 Jun 2005 12:05:35 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">336 at http://www.whijo.net</guid>
</item>
<item>
 <title>Telkom is awesome.</title>
 <link>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html</link>
 <description>&lt;p&gt;In a week we are moving houses. We have a list of things that need to be sorted before we move. So, one of the things on the list is moving our phone line/ADSL. I have a &lt;a href=&quot;http://cybersmart.co.za/adslapplyoneprice.cgi?circuit=384&amp;amp;&amp;amp;cap=3&quot;&gt;one price ADSL from cybersmart&lt;/a&gt; so I can take advantage of their &lt;a href=&quot;http://cybersmart.co.za/nightrider.cgi&quot;&gt;Night Rider&lt;/a&gt; plan. Little did I know, paying for our ADSL portion from another supplier was something Telkom Just Couldn&#039;t Handle during a transfer.&lt;/p&gt;
&lt;p&gt;I phoned Telkom&#039;s 10219 number, and was told I needed to call a random number to have the transfer done, because there was an ADSL linked to it. So we tried the cybersmart route. Mandy phoned and asked if we could have the line moved. Cybersmart wasn&#039;t so sure. So, we tried Telkom again. After some lengthly discussions Telkom came back with the point that the ISP has to initiate the move. So, back to cybersmart. This time cybersmart was helpful, and said I just needed to fax them my details and request, and they could go ahead, provided they had my signature. Cybersmart phoned back a few days later to tell me the fax was quite light, so they couldn&#039;t make out much. After chatting to the very helpful cybersmarter, she said she could transfer the ADSL portion back to Telkom, and then I could handle the transfer, and once it was complete Cybersmart could migrate the line back. Convoluted, but Telkom understandable. Took a few days, but today the migration went through, and round 2 started.&lt;/p&gt;
&lt;p&gt;I phoned Telkom, and the very helpful call centre person started to sort me out. No charges, just time. I thought I was on the home run. Then I pointed out that the line had an ADSL on it, and I wouldn&#039;t mind having the number changed. Apparently it costs R543.23 to transfer an ADSL line between premises. WHAT? But, doing a self-install of ADSL costs R0.00. So, after some protracted negotiations, the path became clear:&lt;/p&gt;
&lt;p&gt;Cancel my ADSL portion (&quot;downgrade&quot; my line), transfer to the new premises (and because there is no ADSL associated I can roll in a phone number change), once the line is active in the new house, then I can re-initiate the ADSL portion for free. WOW.&lt;/p&gt;
&lt;p&gt;It&#039;s like peeling layers off an onion, and then putting the onion back together. Through all of this the call centre people were lank nice and helpful, but the system gets me down.&lt;/p&gt;
&lt;p&gt;Just to re-cap, to have ADSL and a phone line moved to my new house (which is about 1km away from my old one) I have to:&lt;br /&gt;
Instruct Cybersmart to migrate my ADSL back to Telkom, Ask Telkom to cancel my ADSL, Ask Telkom to move my phone line to my new house, Wait for the new line, Ask Telkom to &quot;upgrade&quot; my line to include ADSL, transfer the ADSL back to Cybersmart. Awesome system guys.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/adsl">adsl</category>
 <category domain="http://www.whijo.net/geek-tags/cybersmart">cybersmart</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/tags/south-africa">south africa</category>
 <category domain="http://www.whijo.net/geek-tags/telkom">telkom</category>
 <pubDate>Sat, 22 Nov 2008 05:57:58 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">460 at http://www.whijo.net</guid>
</item>
<item>
 <title>Telkom is awesome.</title>
 <link>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html</link>
 <description>&lt;p&gt;In a week we are moving houses. We have a list of things that need to be sorted before we move. So, one of the things on the list is moving our phone line/ADSL. I have a &lt;a href=&quot;http://cybersmart.co.za/adslapplyoneprice.cgi?circuit=384&amp;amp;&amp;amp;cap=3&quot;&gt;one price ADSL from cybersmart&lt;/a&gt; so I can take advantage of their &lt;a href=&quot;http://cybersmart.co.za/nightrider.cgi&quot;&gt;Night Rider&lt;/a&gt; plan. Little did I know, paying for our ADSL portion from another supplier was something Telkom Just Couldn&#039;t Handle during a transfer.&lt;/p&gt;
&lt;p&gt;I phoned Telkom&#039;s 10219 number, and was told I needed to call a random number to have the transfer done, because there was an ADSL linked to it. So we tried the cybersmart route. Mandy phoned and asked if we could have the line moved. Cybersmart wasn&#039;t so sure. So, we tried Telkom again. After some lengthly discussions Telkom came back with the point that the ISP has to initiate the move. So, back to cybersmart. This time cybersmart was helpful, and said I just needed to fax them my details and request, and they could go ahead, provided they had my signature. Cybersmart phoned back a few days later to tell me the fax was quite light, so they couldn&#039;t make out much. After chatting to the very helpful cybersmarter, she said she could transfer the ADSL portion back to Telkom, and then I could handle the transfer, and once it was complete Cybersmart could migrate the line back. Convoluted, but Telkom understandable. Took a few days, but today the migration went through, and round 2 started.&lt;/p&gt;
&lt;p&gt;I phoned Telkom, and the very helpful call centre person started to sort me out. No charges, just time. I thought I was on the home run. Then I pointed out that the line had an ADSL on it, and I wouldn&#039;t mind having the number changed. Apparently it costs R543.23 to transfer an ADSL line between premises. WHAT? But, doing a self-install of ADSL costs R0.00. So, after some protracted negotiations, the path became clear:&lt;/p&gt;
&lt;p&gt;Cancel my ADSL portion (&quot;downgrade&quot; my line), transfer to the new premises (and because there is no ADSL associated I can roll in a phone number change), once the line is active in the new house, then I can re-initiate the ADSL portion for free. WOW.&lt;/p&gt;
&lt;p&gt;It&#039;s like peeling layers off an onion, and then putting the onion back together. Through all of this the call centre people were lank nice and helpful, but the system gets me down.&lt;/p&gt;
&lt;p&gt;Just to re-cap, to have ADSL and a phone line moved to my new house (which is about 1km away from my old one) I have to:&lt;br /&gt;
Instruct Cybersmart to migrate my ADSL back to Telkom, Ask Telkom to cancel my ADSL, Ask Telkom to move my phone line to my new house, Wait for the new line, Ask Telkom to &quot;upgrade&quot; my line to include ADSL, transfer the ADSL back to Cybersmart. Awesome system guys.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/adsl">adsl</category>
 <category domain="http://www.whijo.net/geek-tags/cybersmart">cybersmart</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/tags/south-africa">south africa</category>
 <category domain="http://www.whijo.net/geek-tags/telkom">telkom</category>
 <pubDate>Sat, 22 Nov 2008 05:57:58 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">460 at http://www.whijo.net</guid>
</item>
<item>
 <title>Behind the scenes</title>
 <link>http://www.whijo.net/blog/brad/2004/11/26/behind-scenes.html</link>
 <description>RUCUS import: 
&lt;p&gt;There is a really cool page of photos &lt;a class=&quot;external&quot; href=&quot;http://www.cpoy.org/59/winimgdisp.php?cat=12&quot;&gt;behind the scenes of the pornography industry&lt;/a&gt;. It is really interesting and there is some really good photography. Nothing terrifically explicit, but there is one or two nipples and bums.&lt;/p&gt;</description>
 <comments>http://www.whijo.net/blog/brad/2004/11/26/behind-scenes.html#comments</comments>
 <pubDate>Fri, 26 Nov 2004 12:22:36 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">225 at http://www.whijo.net</guid>
</item>
<item>
 <title>Its Amaymay!</title>
 <link>http://www.whijo.net/blog/amanda/2008/06/19/its-amaymay.html</link>
 <description>&lt;p&gt;I&#039;ve been meaning to write down these words for such a long time. I&#039;m forced to keep repeating them in my mind so that I don&#039;t forget them, but its time to free up some mental resources and put pen to paper (fingers to keyboard, you know what i mean)!&lt;/p&gt;
&lt;p&gt;Dear Finley&lt;/p&gt;
&lt;p&gt;There are so many everyday things that I love about you at this age that I don&#039;t want to forget. When we were in Grahamstown the other weekend, Dylan passed you the placemat and asked you if you still liked woven things. Your dad and I had completely forgot how much you were fascinated by anything woven when you were a baby. Maybe you were 6 months old or so, (possibly older) and you used to scratch at whatever woven thing you were holding, exploring with your fingers and be captivated in that moment. So that event prompted me to make a list of the things I don&#039;t want to forget about the you that you are now.&lt;/p&gt;
&lt;p&gt;When you sleep on your back, you lie with your hands under your head, elbows sticking out. Just like your great-grandpa Jimmy Ping (so says nana H). I like the idea of being able to see your heritage and ancestry not only by the way you look but by the things you do.&lt;/p&gt;
&lt;p&gt;I love watching you dip things. Whether its a rusk into my cup of tea or a piece of bread into a bowl of soup. its the sweetest thing - the way your little tongue licks whatever you&#039;ve just dipped.&lt;/p&gt;
&lt;p&gt;I love how busy you seem to be. Off on your own mission, enjoying spending time alone, well with baba or george for company. I like how you sometimes go into your room and fetch a book and sit on your bed paging through it. It makes me smile to know that you love reading and that you see your room as part of your space, a haven, a place to retreat to.&lt;/p&gt;
&lt;p&gt;I love all your new words that you are starting to say. Like the other day your dad taught you how to say Its Amazing! and when you say Amaymay! you say it with the same expression as he does. &lt;/p&gt;
&lt;p&gt;I love that you still spend time sleeping in our bed. We both love waking up with you in the middle and in those early morning moments we look at you in amazement, this growing boy lying peacefully between us. We still ooh and aah over your cuteness, how you now roll over and cuddle us, taking turns at throwing an arm over your dad or nuzzling your head onto my pillow.&lt;/p&gt;
&lt;p&gt;I love how you name all the veggies in our vegetable garden and water them as part of your being-outside-rituals, all by yourself without any prompting. I love that you have a sense of responsibility about looking after these plants, that you are gentle with them, and delicately pluck the basil leaves while I am getting ready to make pesto. I love how shopping at Fruit&amp;amp;Veg City excites you in the same way it does me. I love how you exclaim at the size of the butternuts or rip at the packaging of the broccoli, like it is a sweet, delicious treat.&lt;/p&gt;
&lt;p&gt;I love how interested you are in cooking and baking and being with me in the kitchen. You love to see what&#039;s in the pot and love sprinkling in various herbs and spices. I&#039;m forced to curb your enthusiasm otherwise we would have the total contents of the spice rack in every meal. I love that despite your willfulness you let me guide you about which flavours compliment each other and which don&#039;t.&lt;/p&gt;
&lt;p&gt;Most of all, I love watching you become the person you are. You can be strong and fierce when you need to but also so kind and empathetic. Your sense of humour has blossomed in the last 6 months. You make us laugh until we cry and we feel so blessed that you are sharing this life with us. Finley, You are Amaymay!&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/amanda/2008/06/19/its-amaymay.html#comments</comments>
 <category domain="http://www.whijo.net/tags/cape-town-life">cape town life</category>
 <category domain="http://www.whijo.net/tags/cute">cute</category>
 <category domain="http://www.whijo.net/tags/finley">finley</category>
 <category domain="http://www.whijo.net/tags/speaking">speaking</category>
 <pubDate>Thu, 19 Jun 2008 14:10:06 +0200</pubDate>
 <dc:creator>amanda</dc:creator>
 <guid isPermaLink="false">441 at http://www.whijo.net</guid>
</item>
<item>
 <title>Telkom is awesome.</title>
 <link>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html</link>
 <description>&lt;p&gt;In a week we are moving houses. We have a list of things that need to be sorted before we move. So, one of the things on the list is moving our phone line/ADSL. I have a &lt;a href=&quot;http://cybersmart.co.za/adslapplyoneprice.cgi?circuit=384&amp;amp;&amp;amp;cap=3&quot;&gt;one price ADSL from cybersmart&lt;/a&gt; so I can take advantage of their &lt;a href=&quot;http://cybersmart.co.za/nightrider.cgi&quot;&gt;Night Rider&lt;/a&gt; plan. Little did I know, paying for our ADSL portion from another supplier was something Telkom Just Couldn&#039;t Handle during a transfer.&lt;/p&gt;
&lt;p&gt;I phoned Telkom&#039;s 10219 number, and was told I needed to call a random number to have the transfer done, because there was an ADSL linked to it. So we tried the cybersmart route. Mandy phoned and asked if we could have the line moved. Cybersmart wasn&#039;t so sure. So, we tried Telkom again. After some lengthly discussions Telkom came back with the point that the ISP has to initiate the move. So, back to cybersmart. This time cybersmart was helpful, and said I just needed to fax them my details and request, and they could go ahead, provided they had my signature. Cybersmart phoned back a few days later to tell me the fax was quite light, so they couldn&#039;t make out much. After chatting to the very helpful cybersmarter, she said she could transfer the ADSL portion back to Telkom, and then I could handle the transfer, and once it was complete Cybersmart could migrate the line back. Convoluted, but Telkom understandable. Took a few days, but today the migration went through, and round 2 started.&lt;/p&gt;
&lt;p&gt;I phoned Telkom, and the very helpful call centre person started to sort me out. No charges, just time. I thought I was on the home run. Then I pointed out that the line had an ADSL on it, and I wouldn&#039;t mind having the number changed. Apparently it costs R543.23 to transfer an ADSL line between premises. WHAT? But, doing a self-install of ADSL costs R0.00. So, after some protracted negotiations, the path became clear:&lt;/p&gt;
&lt;p&gt;Cancel my ADSL portion (&quot;downgrade&quot; my line), transfer to the new premises (and because there is no ADSL associated I can roll in a phone number change), once the line is active in the new house, then I can re-initiate the ADSL portion for free. WOW.&lt;/p&gt;
&lt;p&gt;It&#039;s like peeling layers off an onion, and then putting the onion back together. Through all of this the call centre people were lank nice and helpful, but the system gets me down.&lt;/p&gt;
&lt;p&gt;Just to re-cap, to have ADSL and a phone line moved to my new house (which is about 1km away from my old one) I have to:&lt;br /&gt;
Instruct Cybersmart to migrate my ADSL back to Telkom, Ask Telkom to cancel my ADSL, Ask Telkom to move my phone line to my new house, Wait for the new line, Ask Telkom to &quot;upgrade&quot; my line to include ADSL, transfer the ADSL back to Cybersmart. Awesome system guys.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/adsl">adsl</category>
 <category domain="http://www.whijo.net/geek-tags/cybersmart">cybersmart</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/tags/south-africa">south africa</category>
 <category domain="http://www.whijo.net/geek-tags/telkom">telkom</category>
 <pubDate>Sat, 22 Nov 2008 05:57:58 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">460 at http://www.whijo.net</guid>
</item>
<item>
 <title>Statistics logging for Django - part 2</title>
 <link>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html</link>
 <description>&lt;p&gt;In &lt;a href=&quot;http://whijo.net/blog/brad/2007/07/19/statistics-logging-django.html&quot;&gt;part 1&lt;/a&gt; I explained how to build middleware and an associated model to capture page accesses, and tie them to a user session. Now that we have all this useful info logged we need to do something with it, like, display it. Unfortunately Django doesn&#039;t have a facility for using GROUP BY with mysql, so you have two major choices (there are more but we can ignore them): implement a custom request in a &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#managers&quot;&gt;custom Manager&lt;/a&gt; (see &lt;a href=&quot;http://www.djangosnippets.org/snippets/236/&quot;&gt;snippet&lt;/a&gt; and &lt;a href=&quot;http://www.djangosnippets.org/snippets/1/&quot;&gt;snippet&lt;/a&gt;, or &lt;a href=&quot;http://www.djangosnippets.org/tags/group-by/&quot;&gt;tagged snippets&lt;/a&gt;), or exploit a &lt;a href=&quot;&quot;&gt;mysql view&lt;/a&gt; and model it in Django. Now for me I prefer the latter because it means my custom sql becomes a mysql customisation and as far as Django is concerned it is dealing with a normal table (but don&#039;t tell Django that it is read only), and thus the model code works, so subsequent queries and manipulations can exploit the &lt;acronym title=&quot;Object Relational Manager&quot;&gt;ORM&lt;/acronym&gt; easily. My subjective and non-scientific experience is that using views is a lot more efficient/quick than using custom queries in the manager (it probably has to do with whatever optimisations exist with views, and the fact that you only fetch items when Django decides you need to fetch a row). So, how the hell do we do it?&lt;/p&gt;
&lt;p&gt;First I created a model that describes what information I want to deal with (something which maps neatly on to our other model):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UserActivity(models.Model):
        session = models.OneToOneField(Session,
                                        db_index=True, 
                                        null=True,blank=True,
                                        primary_key=True)
        user = models.ForeignKey(User,null=True,blank=True)
        date = models.DateTimeField(
                       help_text=&quot;Date Request started processing&quot;,
                       auto_now_add=True,
                       db_index=True)
        processing_time = models.IntegerField(
                       help_text=&quot;Total time spent on this user&quot;)
        requests = models.IntegerField(
                       help_text=&quot;Total Requests in this session&quot;)
        stats = UserActivityManager()
        def __str__(self):
                return &#039;%s: %s %s - %s - %s&#039; % (self.user,self.session,self.date,self.processing_time,self.requests)
        class Admin:
                list_display= (&#039;user&#039;,&#039;session&#039;,&#039;date&#039;,&#039;processing_time&#039;,&#039;requests&#039;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
The nice thing about this set up is when we aggregate our activity logs we can pull out random stuff like total processing time for requests for a user/session, along with number of requests/user/session (and thus average request time)&lt;/p&gt;
&lt;p&gt;But that is just our model, we still need the magic. To implement the magic nicely I put some custom initial SQL into the sql directory of my application (in my case the housing application for this is called accounts, so I make a file called accounts/sql/useractivity.sql), you can read more about initial data &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#providing-initial-sql-data&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;http://www.djangoproject.com/documentation/models/fixtures/&quot;&gt;Django fixtures&lt;/a&gt;).My SQL looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE accounts_useractivity;
CREATE OR REPLACE VIEW accounts_useractivity AS 
SELECT i.session_id,
       i.user_id,
       MAX(i.date) as date,
       sum(i.request_time) AS processing_time, 
       count(*) AS requests 
FROM accounts_activitylog i 
GROUP BY 1 
ORDER BY NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So first I tell mysql to drop the table that django just created (accounts_useractivity), and create a view in it&#039;s place. The view is very simple, in that it just GROUP BY the session_id. The real hair puller for me was figuring out that I needed to use the MAX(i.date) (see more about &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html&quot;&gt;aggregate functions&lt;/a&gt;) to get the most recent access to float to the top when it normalises the data (otherwise the GROUP BY normally ORDER BY the session_id, which helps no one), the ORDER BY NULL is &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html&quot;&gt;an optimisation&lt;/a&gt; to tell GROUP BY not to ORDER BY. I am hoping that because date is an INDEX (from our logging model) it shouldn&#039;t cost too much to do a MAX. (I would like someone with Much MYSQL-fu to point out any further optimisations to this, or even alternative approaches to the whole thing).&lt;/p&gt;
&lt;p&gt;So now we have an aggregating VIEW which Django maps using it&#039;s ORM, so that to figure out sessions which have been active in the last x minutes (where x is a datetime.timedelta object) we simply do a:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UserActivity.objects.get_query_set().filter(date__gte=datetime.now()-x)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
I wrote a custom manager for getting recent sessions etc., but that is an exercise for the reader. What I did include in my model is something which returns a stepped &quot;request_weight&quot; i.e. session requests / largest session request x steps, which in my case defaults to 6. This means I can style my users like one would a &quot;&lt;a href=&quot;http://en.wikipedia.org/wiki/Tag_cloud&quot;&gt;tag cloud&lt;/a&gt;&quot;, so very active sessions will grow bigger than less active sessions. I needed to implement a helper function in the custom manager to return the session with the most requests.&lt;/p&gt;
&lt;p&gt;The final tip is to use a &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#subclassing-context-requestcontext&quot;&gt;context processor&lt;/a&gt; to make the information available to all your templates, although you could do it with middleware (maybe middleware is the proper way to do it?).&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/django">django</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/geek-tags/middleware">middleware</category>
 <category domain="http://www.whijo.net/geek-tags/mysql">mysql</category>
 <category domain="http://www.whijo.net/geek-tags/mysql-views">mysql views</category>
 <category domain="http://www.whijo.net/geek-tags/python">python</category>
 <category domain="http://www.whijo.net/geek-tags/statistics">statistics</category>
 <pubDate>Sun, 29 Jul 2007 21:52:25 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">110 at http://www.whijo.net</guid>
</item>
<item>
 <title>Statistics logging for Django - part 2</title>
 <link>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html</link>
 <description>&lt;p&gt;In &lt;a href=&quot;http://whijo.net/blog/brad/2007/07/19/statistics-logging-django.html&quot;&gt;part 1&lt;/a&gt; I explained how to build middleware and an associated model to capture page accesses, and tie them to a user session. Now that we have all this useful info logged we need to do something with it, like, display it. Unfortunately Django doesn&#039;t have a facility for using GROUP BY with mysql, so you have two major choices (there are more but we can ignore them): implement a custom request in a &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#managers&quot;&gt;custom Manager&lt;/a&gt; (see &lt;a href=&quot;http://www.djangosnippets.org/snippets/236/&quot;&gt;snippet&lt;/a&gt; and &lt;a href=&quot;http://www.djangosnippets.org/snippets/1/&quot;&gt;snippet&lt;/a&gt;, or &lt;a href=&quot;http://www.djangosnippets.org/tags/group-by/&quot;&gt;tagged snippets&lt;/a&gt;), or exploit a &lt;a href=&quot;&quot;&gt;mysql view&lt;/a&gt; and model it in Django. Now for me I prefer the latter because it means my custom sql becomes a mysql customisation and as far as Django is concerned it is dealing with a normal table (but don&#039;t tell Django that it is read only), and thus the model code works, so subsequent queries and manipulations can exploit the &lt;acronym title=&quot;Object Relational Manager&quot;&gt;ORM&lt;/acronym&gt; easily. My subjective and non-scientific experience is that using views is a lot more efficient/quick than using custom queries in the manager (it probably has to do with whatever optimisations exist with views, and the fact that you only fetch items when Django decides you need to fetch a row). So, how the hell do we do it?&lt;/p&gt;
&lt;p&gt;First I created a model that describes what information I want to deal with (something which maps neatly on to our other model):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UserActivity(models.Model):
        session = models.OneToOneField(Session,
                                        db_index=True, 
                                        null=True,blank=True,
                                        primary_key=True)
        user = models.ForeignKey(User,null=True,blank=True)
        date = models.DateTimeField(
                       help_text=&quot;Date Request started processing&quot;,
                       auto_now_add=True,
                       db_index=True)
        processing_time = models.IntegerField(
                       help_text=&quot;Total time spent on this user&quot;)
        requests = models.IntegerField(
                       help_text=&quot;Total Requests in this session&quot;)
        stats = UserActivityManager()
        def __str__(self):
                return &#039;%s: %s %s - %s - %s&#039; % (self.user,self.session,self.date,self.processing_time,self.requests)
        class Admin:
                list_display= (&#039;user&#039;,&#039;session&#039;,&#039;date&#039;,&#039;processing_time&#039;,&#039;requests&#039;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
The nice thing about this set up is when we aggregate our activity logs we can pull out random stuff like total processing time for requests for a user/session, along with number of requests/user/session (and thus average request time)&lt;/p&gt;
&lt;p&gt;But that is just our model, we still need the magic. To implement the magic nicely I put some custom initial SQL into the sql directory of my application (in my case the housing application for this is called accounts, so I make a file called accounts/sql/useractivity.sql), you can read more about initial data &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#providing-initial-sql-data&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;http://www.djangoproject.com/documentation/models/fixtures/&quot;&gt;Django fixtures&lt;/a&gt;).My SQL looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE accounts_useractivity;
CREATE OR REPLACE VIEW accounts_useractivity AS 
SELECT i.session_id,
       i.user_id,
       MAX(i.date) as date,
       sum(i.request_time) AS processing_time, 
       count(*) AS requests 
FROM accounts_activitylog i 
GROUP BY 1 
ORDER BY NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So first I tell mysql to drop the table that django just created (accounts_useractivity), and create a view in it&#039;s place. The view is very simple, in that it just GROUP BY the session_id. The real hair puller for me was figuring out that I needed to use the MAX(i.date) (see more about &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html&quot;&gt;aggregate functions&lt;/a&gt;) to get the most recent access to float to the top when it normalises the data (otherwise the GROUP BY normally ORDER BY the session_id, which helps no one), the ORDER BY NULL is &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html&quot;&gt;an optimisation&lt;/a&gt; to tell GROUP BY not to ORDER BY. I am hoping that because date is an INDEX (from our logging model) it shouldn&#039;t cost too much to do a MAX. (I would like someone with Much MYSQL-fu to point out any further optimisations to this, or even alternative approaches to the whole thing).&lt;/p&gt;
&lt;p&gt;So now we have an aggregating VIEW which Django maps using it&#039;s ORM, so that to figure out sessions which have been active in the last x minutes (where x is a datetime.timedelta object) we simply do a:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UserActivity.objects.get_query_set().filter(date__gte=datetime.now()-x)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
I wrote a custom manager for getting recent sessions etc., but that is an exercise for the reader. What I did include in my model is something which returns a stepped &quot;request_weight&quot; i.e. session requests / largest session request x steps, which in my case defaults to 6. This means I can style my users like one would a &quot;&lt;a href=&quot;http://en.wikipedia.org/wiki/Tag_cloud&quot;&gt;tag cloud&lt;/a&gt;&quot;, so very active sessions will grow bigger than less active sessions. I needed to implement a helper function in the custom manager to return the session with the most requests.&lt;/p&gt;
&lt;p&gt;The final tip is to use a &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#subclassing-context-requestcontext&quot;&gt;context processor&lt;/a&gt; to make the information available to all your templates, although you could do it with middleware (maybe middleware is the proper way to do it?).&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/django">django</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/geek-tags/middleware">middleware</category>
 <category domain="http://www.whijo.net/geek-tags/mysql">mysql</category>
 <category domain="http://www.whijo.net/geek-tags/mysql-views">mysql views</category>
 <category domain="http://www.whijo.net/geek-tags/python">python</category>
 <category domain="http://www.whijo.net/geek-tags/statistics">statistics</category>
 <pubDate>Sun, 29 Jul 2007 21:52:25 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">110 at http://www.whijo.net</guid>
</item>
<item>
 <title>Telkom is awesome.</title>
 <link>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html</link>
 <description>&lt;p&gt;In a week we are moving houses. We have a list of things that need to be sorted before we move. So, one of the things on the list is moving our phone line/ADSL. I have a &lt;a href=&quot;http://cybersmart.co.za/adslapplyoneprice.cgi?circuit=384&amp;amp;&amp;amp;cap=3&quot;&gt;one price ADSL from cybersmart&lt;/a&gt; so I can take advantage of their &lt;a href=&quot;http://cybersmart.co.za/nightrider.cgi&quot;&gt;Night Rider&lt;/a&gt; plan. Little did I know, paying for our ADSL portion from another supplier was something Telkom Just Couldn&#039;t Handle during a transfer.&lt;/p&gt;
&lt;p&gt;I phoned Telkom&#039;s 10219 number, and was told I needed to call a random number to have the transfer done, because there was an ADSL linked to it. So we tried the cybersmart route. Mandy phoned and asked if we could have the line moved. Cybersmart wasn&#039;t so sure. So, we tried Telkom again. After some lengthly discussions Telkom came back with the point that the ISP has to initiate the move. So, back to cybersmart. This time cybersmart was helpful, and said I just needed to fax them my details and request, and they could go ahead, provided they had my signature. Cybersmart phoned back a few days later to tell me the fax was quite light, so they couldn&#039;t make out much. After chatting to the very helpful cybersmarter, she said she could transfer the ADSL portion back to Telkom, and then I could handle the transfer, and once it was complete Cybersmart could migrate the line back. Convoluted, but Telkom understandable. Took a few days, but today the migration went through, and round 2 started.&lt;/p&gt;
&lt;p&gt;I phoned Telkom, and the very helpful call centre person started to sort me out. No charges, just time. I thought I was on the home run. Then I pointed out that the line had an ADSL on it, and I wouldn&#039;t mind having the number changed. Apparently it costs R543.23 to transfer an ADSL line between premises. WHAT? But, doing a self-install of ADSL costs R0.00. So, after some protracted negotiations, the path became clear:&lt;/p&gt;
&lt;p&gt;Cancel my ADSL portion (&quot;downgrade&quot; my line), transfer to the new premises (and because there is no ADSL associated I can roll in a phone number change), once the line is active in the new house, then I can re-initiate the ADSL portion for free. WOW.&lt;/p&gt;
&lt;p&gt;It&#039;s like peeling layers off an onion, and then putting the onion back together. Through all of this the call centre people were lank nice and helpful, but the system gets me down.&lt;/p&gt;
&lt;p&gt;Just to re-cap, to have ADSL and a phone line moved to my new house (which is about 1km away from my old one) I have to:&lt;br /&gt;
Instruct Cybersmart to migrate my ADSL back to Telkom, Ask Telkom to cancel my ADSL, Ask Telkom to move my phone line to my new house, Wait for the new line, Ask Telkom to &quot;upgrade&quot; my line to include ADSL, transfer the ADSL back to Cybersmart. Awesome system guys.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/adsl">adsl</category>
 <category domain="http://www.whijo.net/geek-tags/cybersmart">cybersmart</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/tags/south-africa">south africa</category>
 <category domain="http://www.whijo.net/geek-tags/telkom">telkom</category>
 <pubDate>Sat, 22 Nov 2008 05:57:58 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">460 at http://www.whijo.net</guid>
</item>
<item>
 <title>Mambo developers smoke crack, sometimes</title>
 <link>http://www.whijo.net/blog/brad/2005/06/30/mambo-developers-smoke-crack-sometimes.html</link>
 <description>RUCUS import: geeking,www
&lt;p&gt;After using &lt;a href=&quot;http://www.mamboserver.com&quot; class=&quot;external&quot;&gt;mambo&lt;/a&gt; for awhile it is clear that the mambo developers either don&#039;t have the time to, or don&#039;t really understand the power that can be gotten out of PHP, html, css, etc. There is a lot of instances where there is heaps of code to do something, and it could be simplified and shortened by just catering for a proper base case, and using things like the .= operator (for strings, for e.g.). The com_rss module is also on crack a bit, but i have hacked it a bit so that it is not as barmy, and now produces a decent feed for the &lt;a href=&quot;http://www.go-opensource.org&quot; class=&quot;external&quot;&gt;GOS&lt;/a&gt; site. It just annoys me that I have to dive into core source code to change things that should be exposed in the stylesheet, etc.&lt;/p&gt;
&lt;p&gt;That said, there is a lot of stuff that is pretty good, and to be honest &quot;fixing&quot; things usually is pretty straightforward, the code is not terrifically convoluted. It seems like the code and interface are undergoing major rennovations anyways, so it is getting there, for now there are a lot of things that have taken much hacking to set straight. And the html it produces is still pretty hideous. Getting there slowly though i guess.&lt;/p&gt;</description>
 <comments>http://www.whijo.net/blog/brad/2005/06/30/mambo-developers-smoke-crack-sometimes.html#comments</comments>
 <pubDate>Thu, 30 Jun 2005 12:05:35 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">336 at http://www.whijo.net</guid>
</item>
<item>
 <title>Telkom is awesome.</title>
 <link>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html</link>
 <description>&lt;p&gt;In a week we are moving houses. We have a list of things that need to be sorted before we move. So, one of the things on the list is moving our phone line/ADSL. I have a &lt;a href=&quot;http://cybersmart.co.za/adslapplyoneprice.cgi?circuit=384&amp;amp;&amp;amp;cap=3&quot;&gt;one price ADSL from cybersmart&lt;/a&gt; so I can take advantage of their &lt;a href=&quot;http://cybersmart.co.za/nightrider.cgi&quot;&gt;Night Rider&lt;/a&gt; plan. Little did I know, paying for our ADSL portion from another supplier was something Telkom Just Couldn&#039;t Handle during a transfer.&lt;/p&gt;
&lt;p&gt;I phoned Telkom&#039;s 10219 number, and was told I needed to call a random number to have the transfer done, because there was an ADSL linked to it. So we tried the cybersmart route. Mandy phoned and asked if we could have the line moved. Cybersmart wasn&#039;t so sure. So, we tried Telkom again. After some lengthly discussions Telkom came back with the point that the ISP has to initiate the move. So, back to cybersmart. This time cybersmart was helpful, and said I just needed to fax them my details and request, and they could go ahead, provided they had my signature. Cybersmart phoned back a few days later to tell me the fax was quite light, so they couldn&#039;t make out much. After chatting to the very helpful cybersmarter, she said she could transfer the ADSL portion back to Telkom, and then I could handle the transfer, and once it was complete Cybersmart could migrate the line back. Convoluted, but Telkom understandable. Took a few days, but today the migration went through, and round 2 started.&lt;/p&gt;
&lt;p&gt;I phoned Telkom, and the very helpful call centre person started to sort me out. No charges, just time. I thought I was on the home run. Then I pointed out that the line had an ADSL on it, and I wouldn&#039;t mind having the number changed. Apparently it costs R543.23 to transfer an ADSL line between premises. WHAT? But, doing a self-install of ADSL costs R0.00. So, after some protracted negotiations, the path became clear:&lt;/p&gt;
&lt;p&gt;Cancel my ADSL portion (&quot;downgrade&quot; my line), transfer to the new premises (and because there is no ADSL associated I can roll in a phone number change), once the line is active in the new house, then I can re-initiate the ADSL portion for free. WOW.&lt;/p&gt;
&lt;p&gt;It&#039;s like peeling layers off an onion, and then putting the onion back together. Through all of this the call centre people were lank nice and helpful, but the system gets me down.&lt;/p&gt;
&lt;p&gt;Just to re-cap, to have ADSL and a phone line moved to my new house (which is about 1km away from my old one) I have to:&lt;br /&gt;
Instruct Cybersmart to migrate my ADSL back to Telkom, Ask Telkom to cancel my ADSL, Ask Telkom to move my phone line to my new house, Wait for the new line, Ask Telkom to &quot;upgrade&quot; my line to include ADSL, transfer the ADSL back to Cybersmart. Awesome system guys.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/adsl">adsl</category>
 <category domain="http://www.whijo.net/geek-tags/cybersmart">cybersmart</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/tags/south-africa">south africa</category>
 <category domain="http://www.whijo.net/geek-tags/telkom">telkom</category>
 <pubDate>Sat, 22 Nov 2008 05:57:58 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">460 at http://www.whijo.net</guid>
</item>
<item>
 <title>Statistics logging for Django - part 2</title>
 <link>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html</link>
 <description>&lt;p&gt;In &lt;a href=&quot;http://whijo.net/blog/brad/2007/07/19/statistics-logging-django.html&quot;&gt;part 1&lt;/a&gt; I explained how to build middleware and an associated model to capture page accesses, and tie them to a user session. Now that we have all this useful info logged we need to do something with it, like, display it. Unfortunately Django doesn&#039;t have a facility for using GROUP BY with mysql, so you have two major choices (there are more but we can ignore them): implement a custom request in a &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#managers&quot;&gt;custom Manager&lt;/a&gt; (see &lt;a href=&quot;http://www.djangosnippets.org/snippets/236/&quot;&gt;snippet&lt;/a&gt; and &lt;a href=&quot;http://www.djangosnippets.org/snippets/1/&quot;&gt;snippet&lt;/a&gt;, or &lt;a href=&quot;http://www.djangosnippets.org/tags/group-by/&quot;&gt;tagged snippets&lt;/a&gt;), or exploit a &lt;a href=&quot;&quot;&gt;mysql view&lt;/a&gt; and model it in Django. Now for me I prefer the latter because it means my custom sql becomes a mysql customisation and as far as Django is concerned it is dealing with a normal table (but don&#039;t tell Django that it is read only), and thus the model code works, so subsequent queries and manipulations can exploit the &lt;acronym title=&quot;Object Relational Manager&quot;&gt;ORM&lt;/acronym&gt; easily. My subjective and non-scientific experience is that using views is a lot more efficient/quick than using custom queries in the manager (it probably has to do with whatever optimisations exist with views, and the fact that you only fetch items when Django decides you need to fetch a row). So, how the hell do we do it?&lt;/p&gt;
&lt;p&gt;First I created a model that describes what information I want to deal with (something which maps neatly on to our other model):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UserActivity(models.Model):
        session = models.OneToOneField(Session,
                                        db_index=True, 
                                        null=True,blank=True,
                                        primary_key=True)
        user = models.ForeignKey(User,null=True,blank=True)
        date = models.DateTimeField(
                       help_text=&quot;Date Request started processing&quot;,
                       auto_now_add=True,
                       db_index=True)
        processing_time = models.IntegerField(
                       help_text=&quot;Total time spent on this user&quot;)
        requests = models.IntegerField(
                       help_text=&quot;Total Requests in this session&quot;)
        stats = UserActivityManager()
        def __str__(self):
                return &#039;%s: %s %s - %s - %s&#039; % (self.user,self.session,self.date,self.processing_time,self.requests)
        class Admin:
                list_display= (&#039;user&#039;,&#039;session&#039;,&#039;date&#039;,&#039;processing_time&#039;,&#039;requests&#039;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
The nice thing about this set up is when we aggregate our activity logs we can pull out random stuff like total processing time for requests for a user/session, along with number of requests/user/session (and thus average request time)&lt;/p&gt;
&lt;p&gt;But that is just our model, we still need the magic. To implement the magic nicely I put some custom initial SQL into the sql directory of my application (in my case the housing application for this is called accounts, so I make a file called accounts/sql/useractivity.sql), you can read more about initial data &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#providing-initial-sql-data&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;http://www.djangoproject.com/documentation/models/fixtures/&quot;&gt;Django fixtures&lt;/a&gt;).My SQL looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE accounts_useractivity;
CREATE OR REPLACE VIEW accounts_useractivity AS 
SELECT i.session_id,
       i.user_id,
       MAX(i.date) as date,
       sum(i.request_time) AS processing_time, 
       count(*) AS requests 
FROM accounts_activitylog i 
GROUP BY 1 
ORDER BY NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So first I tell mysql to drop the table that django just created (accounts_useractivity), and create a view in it&#039;s place. The view is very simple, in that it just GROUP BY the session_id. The real hair puller for me was figuring out that I needed to use the MAX(i.date) (see more about &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html&quot;&gt;aggregate functions&lt;/a&gt;) to get the most recent access to float to the top when it normalises the data (otherwise the GROUP BY normally ORDER BY the session_id, which helps no one), the ORDER BY NULL is &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html&quot;&gt;an optimisation&lt;/a&gt; to tell GROUP BY not to ORDER BY. I am hoping that because date is an INDEX (from our logging model) it shouldn&#039;t cost too much to do a MAX. (I would like someone with Much MYSQL-fu to point out any further optimisations to this, or even alternative approaches to the whole thing).&lt;/p&gt;
&lt;p&gt;So now we have an aggregating VIEW which Django maps using it&#039;s ORM, so that to figure out sessions which have been active in the last x minutes (where x is a datetime.timedelta object) we simply do a:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UserActivity.objects.get_query_set().filter(date__gte=datetime.now()-x)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
I wrote a custom manager for getting recent sessions etc., but that is an exercise for the reader. What I did include in my model is something which returns a stepped &quot;request_weight&quot; i.e. session requests / largest session request x steps, which in my case defaults to 6. This means I can style my users like one would a &quot;&lt;a href=&quot;http://en.wikipedia.org/wiki/Tag_cloud&quot;&gt;tag cloud&lt;/a&gt;&quot;, so very active sessions will grow bigger than less active sessions. I needed to implement a helper function in the custom manager to return the session with the most requests.&lt;/p&gt;
&lt;p&gt;The final tip is to use a &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#subclassing-context-requestcontext&quot;&gt;context processor&lt;/a&gt; to make the information available to all your templates, although you could do it with middleware (maybe middleware is the proper way to do it?).&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/django">django</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/geek-tags/middleware">middleware</category>
 <category domain="http://www.whijo.net/geek-tags/mysql">mysql</category>
 <category domain="http://www.whijo.net/geek-tags/mysql-views">mysql views</category>
 <category domain="http://www.whijo.net/geek-tags/python">python</category>
 <category domain="http://www.whijo.net/geek-tags/statistics">statistics</category>
 <pubDate>Sun, 29 Jul 2007 21:52:25 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">110 at http://www.whijo.net</guid>
</item>
<item>
 <title>Telkom is awesome.</title>
 <link>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html</link>
 <description>&lt;p&gt;In a week we are moving houses. We have a list of things that need to be sorted before we move. So, one of the things on the list is moving our phone line/ADSL. I have a &lt;a href=&quot;http://cybersmart.co.za/adslapplyoneprice.cgi?circuit=384&amp;amp;&amp;amp;cap=3&quot;&gt;one price ADSL from cybersmart&lt;/a&gt; so I can take advantage of their &lt;a href=&quot;http://cybersmart.co.za/nightrider.cgi&quot;&gt;Night Rider&lt;/a&gt; plan. Little did I know, paying for our ADSL portion from another supplier was something Telkom Just Couldn&#039;t Handle during a transfer.&lt;/p&gt;
&lt;p&gt;I phoned Telkom&#039;s 10219 number, and was told I needed to call a random number to have the transfer done, because there was an ADSL linked to it. So we tried the cybersmart route. Mandy phoned and asked if we could have the line moved. Cybersmart wasn&#039;t so sure. So, we tried Telkom again. After some lengthly discussions Telkom came back with the point that the ISP has to initiate the move. So, back to cybersmart. This time cybersmart was helpful, and said I just needed to fax them my details and request, and they could go ahead, provided they had my signature. Cybersmart phoned back a few days later to tell me the fax was quite light, so they couldn&#039;t make out much. After chatting to the very helpful cybersmarter, she said she could transfer the ADSL portion back to Telkom, and then I could handle the transfer, and once it was complete Cybersmart could migrate the line back. Convoluted, but Telkom understandable. Took a few days, but today the migration went through, and round 2 started.&lt;/p&gt;
&lt;p&gt;I phoned Telkom, and the very helpful call centre person started to sort me out. No charges, just time. I thought I was on the home run. Then I pointed out that the line had an ADSL on it, and I wouldn&#039;t mind having the number changed. Apparently it costs R543.23 to transfer an ADSL line between premises. WHAT? But, doing a self-install of ADSL costs R0.00. So, after some protracted negotiations, the path became clear:&lt;/p&gt;
&lt;p&gt;Cancel my ADSL portion (&quot;downgrade&quot; my line), transfer to the new premises (and because there is no ADSL associated I can roll in a phone number change), once the line is active in the new house, then I can re-initiate the ADSL portion for free. WOW.&lt;/p&gt;
&lt;p&gt;It&#039;s like peeling layers off an onion, and then putting the onion back together. Through all of this the call centre people were lank nice and helpful, but the system gets me down.&lt;/p&gt;
&lt;p&gt;Just to re-cap, to have ADSL and a phone line moved to my new house (which is about 1km away from my old one) I have to:&lt;br /&gt;
Instruct Cybersmart to migrate my ADSL back to Telkom, Ask Telkom to cancel my ADSL, Ask Telkom to move my phone line to my new house, Wait for the new line, Ask Telkom to &quot;upgrade&quot; my line to include ADSL, transfer the ADSL back to Cybersmart. Awesome system guys.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2008/11/22/telkom-awesome.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/adsl">adsl</category>
 <category domain="http://www.whijo.net/geek-tags/cybersmart">cybersmart</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/tags/south-africa">south africa</category>
 <category domain="http://www.whijo.net/geek-tags/telkom">telkom</category>
 <pubDate>Sat, 22 Nov 2008 05:57:58 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">460 at http://www.whijo.net</guid>
</item>
<item>
 <title>ZOMG! BidOrBuy iz offer R300 for ma blogs!</title>
 <link>http://www.whijo.net/blog/brad/2008/10/29/zomg-bidorbuy-iz-offer-r300-ma-blogs.html</link>
 <description>&lt;p&gt;&lt;a href=&quot;http://www.bidorbuy.co.za&quot;&gt;Bidorbuy&lt;/a&gt; has offered me &lt;em&gt;The Chance&lt;/em&gt; to win &lt;em&gt;&lt;strong&gt;Three Hundred South African Rands&lt;/strong&gt;&lt;/em&gt; by &lt;a href=&quot;http://www.bidorbuy.co.za/jsp/promo/blog/bloggersBestGuess.jsp&quot;&gt;blogging about&lt;/a&gt; how much &lt;em&gt;I&lt;/em&gt; think &lt;a href=&quot;http://www.bidorbuy.co.za/jsp/item/Item.jsp?Trade_TradeId=9772841&quot;&gt;a car&lt;/a&gt; will be auctioned for. I personally think it will be auctioned for &lt;em&gt;One Million South African Rand&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I think it is fun that bidorbuy is trying to use viral marketing to attract more traffic. But, when I see that they are offering (a chance to win) a prize of &lt;em&gt;&lt;strong&gt;Three Hundred South African Rands&lt;/strong&gt;&lt;/em&gt; to generate &#039;buzz&#039; around an auction for a car valued at &lt;abbr title=&quot;R124,617.70&quot;&gt;One Hundred and Twenty Four Thousand, Six Hundred and Seventeen Rand (and Seventy cents)&lt;/abbr&gt; I get a bit indignant. Seriously? You are offering ONE blogger a prize of R300, and expect buzz to follow? THREE HUNDRED RAND?&lt;/p&gt;
&lt;p&gt;So, to every one of my readers, far and wide as you may be, please enter this ALLURING competition to win THREE HUNDRED RAND for &lt;a href=&quot;http://www.imod.co.za&quot;&gt;pimping your blog out to the lowest bidder&lt;/a&gt;.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2008/10/29/zomg-bidorbuy-iz-offer-r300-ma-blogs.html#comments</comments>
 <category domain="http://www.whijo.net/tags/bidorbuy">bidorbuy</category>
 <category domain="http://www.whijo.net/tags/marketing">marketing</category>
 <category domain="http://www.whijo.net/tags/viral-marketing">viral marketing</category>
 <pubDate>Wed, 29 Oct 2008 14:55:30 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">456 at http://www.whijo.net</guid>
</item>
<item>
 <title>Buddha bubbles</title>
 <link>http://www.whijo.net/blog/amanda/2008/09/15/buddha-bubbles.html</link>
 <description>&lt;p&gt;&lt;em&gt;&quot;We should not complain about impermanence, because without impermanence, nothing is possible&quot;&lt;/em&gt; - Thich Nhat Hanh&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/buddha.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: buddha.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/buddha.jpg&quot; alt=&quot;buddha.jpg&quot; title=&quot;buddha.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;em&gt;Picture taken by Finley&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It was Saturday morning and we had just finished eating our fruit salad when Finley&#039;s eye caught the bottle of bubbles on the shelf. He&#039;s become really good at blowing bubbles and holding the bottle by himself, dip and blow. &lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles2.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles2.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles2.jpg&quot; alt=&quot;bubbles2.jpg&quot; title=&quot;bubbles2.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles1.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles1.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles1.jpg&quot; alt=&quot;bubbles1.jpg&quot; title=&quot;bubbles1.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles3.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles3.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles3.jpg&quot; alt=&quot;bubbles3.jpg&quot; title=&quot;bubbles3.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;em&gt;Dip. Dip. Blow.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;He has also developed this high-pitched three-note melody that goes something like &quot;aaahuhaah&quot; when the bubble he is blowing pops before it leaves the bubble wand. The cuteness of that sound never gets old.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles4.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles4.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles4.jpg&quot; alt=&quot;bubbles4.jpg&quot; title=&quot;bubbles4.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;em&gt;This is the shape of that sound.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This Saturday though, he was becoming increasingly troubled by those bubbles that did make it off the wand to float gracefully in the air, sunlight shimmering off their soapy skins, only to pop and sadly disappear as if they were never there.&lt;/p&gt;
&lt;p&gt;I started talking to him about Buddha&#039;s ideas on impermanence and how we can appreciate the beauty of the bubbles without getting too attached to them. I think he stopped listening after I said Buddha and seemingly my deep life lesson fell on deaf ears. Instead, he went over to the bookshelf and pointed up at our own Buddha who has regrettably become somewhat of a bookend (only temporary I promise). &quot;Booda Booda!&quot; he cried in excitement. I went over to fetch it for him and he proceeded to happily blow bubbles with Buddha, and then happily pop the bubbles with Buddha as well. For the rest of the day, Finley and Buddha played together, ate together and drank together. I love being reminded, when trying to &#039;teach&#039; Finley something, or impart some wisdom, that really, he is the one who is constantly teaching me.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/amanda/2008/09/15/buddha-bubbles.html#comments</comments>
 <category domain="http://www.whijo.net/tags/cape-town-life">cape town life</category>
 <category domain="http://www.whijo.net/tags/finley">finley</category>
 <category domain="http://www.whijo.net/tags/life-lessons">life lessons</category>
 <pubDate>Mon, 15 Sep 2008 14:21:54 +0200</pubDate>
 <dc:creator>amanda</dc:creator>
 <guid isPermaLink="false">450 at http://www.whijo.net</guid>
</item>
<item>
 <title>Buddha bubbles</title>
 <link>http://www.whijo.net/blog/amanda/2008/09/15/buddha-bubbles.html</link>
 <description>&lt;p&gt;&lt;em&gt;&quot;We should not complain about impermanence, because without impermanence, nothing is possible&quot;&lt;/em&gt; - Thich Nhat Hanh&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/buddha.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: buddha.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/buddha.jpg&quot; alt=&quot;buddha.jpg&quot; title=&quot;buddha.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;em&gt;Picture taken by Finley&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It was Saturday morning and we had just finished eating our fruit salad when Finley&#039;s eye caught the bottle of bubbles on the shelf. He&#039;s become really good at blowing bubbles and holding the bottle by himself, dip and blow. &lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles2.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles2.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles2.jpg&quot; alt=&quot;bubbles2.jpg&quot; title=&quot;bubbles2.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles1.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles1.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles1.jpg&quot; alt=&quot;bubbles1.jpg&quot; title=&quot;bubbles1.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles3.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles3.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles3.jpg&quot; alt=&quot;bubbles3.jpg&quot; title=&quot;bubbles3.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;em&gt;Dip. Dip. Blow.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;He has also developed this high-pitched three-note melody that goes something like &quot;aaahuhaah&quot; when the bubble he is blowing pops before it leaves the bubble wand. The cuteness of that sound never gets old.&lt;/p&gt;
&lt;p&gt;&lt;span class=&quot;inline&quot;&gt;&lt;a href=&quot;http://www.whijo.net/files/bubbles4.jpg&quot; class=&quot;inline-image-link&quot; title=&quot;View: bubbles4.jpg&quot; rel=&quot;lightbox[gp_inline]&quot;&gt;&lt;img src=&quot;http://www.whijo.net/files/imagecache/inline_resize/files/bubbles4.jpg&quot; alt=&quot;bubbles4.jpg&quot; title=&quot;bubbles4.jpg&quot;  class=&quot;inline&quot; /&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;
&lt;em&gt;This is the shape of that sound.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This Saturday though, he was becoming increasingly troubled by those bubbles that did make it off the wand to float gracefully in the air, sunlight shimmering off their soapy skins, only to pop and sadly disappear as if they were never there.&lt;/p&gt;
&lt;p&gt;I started talking to him about Buddha&#039;s ideas on impermanence and how we can appreciate the beauty of the bubbles without getting too attached to them. I think he stopped listening after I said Buddha and seemingly my deep life lesson fell on deaf ears. Instead, he went over to the bookshelf and pointed up at our own Buddha who has regrettably become somewhat of a bookend (only temporary I promise). &quot;Booda Booda!&quot; he cried in excitement. I went over to fetch it for him and he proceeded to happily blow bubbles with Buddha, and then happily pop the bubbles with Buddha as well. For the rest of the day, Finley and Buddha played together, ate together and drank together. I love being reminded, when trying to &#039;teach&#039; Finley something, or impart some wisdom, that really, he is the one who is constantly teaching me.&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/amanda/2008/09/15/buddha-bubbles.html#comments</comments>
 <category domain="http://www.whijo.net/tags/cape-town-life">cape town life</category>
 <category domain="http://www.whijo.net/tags/finley">finley</category>
 <category domain="http://www.whijo.net/tags/life-lessons">life lessons</category>
 <pubDate>Mon, 15 Sep 2008 14:21:54 +0200</pubDate>
 <dc:creator>amanda</dc:creator>
 <guid isPermaLink="false">450 at http://www.whijo.net</guid>
</item>
<item>
 <title>Statistics logging for Django - part 2</title>
 <link>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html</link>
 <description>&lt;p&gt;In &lt;a href=&quot;http://whijo.net/blog/brad/2007/07/19/statistics-logging-django.html&quot;&gt;part 1&lt;/a&gt; I explained how to build middleware and an associated model to capture page accesses, and tie them to a user session. Now that we have all this useful info logged we need to do something with it, like, display it. Unfortunately Django doesn&#039;t have a facility for using GROUP BY with mysql, so you have two major choices (there are more but we can ignore them): implement a custom request in a &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#managers&quot;&gt;custom Manager&lt;/a&gt; (see &lt;a href=&quot;http://www.djangosnippets.org/snippets/236/&quot;&gt;snippet&lt;/a&gt; and &lt;a href=&quot;http://www.djangosnippets.org/snippets/1/&quot;&gt;snippet&lt;/a&gt;, or &lt;a href=&quot;http://www.djangosnippets.org/tags/group-by/&quot;&gt;tagged snippets&lt;/a&gt;), or exploit a &lt;a href=&quot;&quot;&gt;mysql view&lt;/a&gt; and model it in Django. Now for me I prefer the latter because it means my custom sql becomes a mysql customisation and as far as Django is concerned it is dealing with a normal table (but don&#039;t tell Django that it is read only), and thus the model code works, so subsequent queries and manipulations can exploit the &lt;acronym title=&quot;Object Relational Manager&quot;&gt;ORM&lt;/acronym&gt; easily. My subjective and non-scientific experience is that using views is a lot more efficient/quick than using custom queries in the manager (it probably has to do with whatever optimisations exist with views, and the fact that you only fetch items when Django decides you need to fetch a row). So, how the hell do we do it?&lt;/p&gt;
&lt;p&gt;First I created a model that describes what information I want to deal with (something which maps neatly on to our other model):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UserActivity(models.Model):
        session = models.OneToOneField(Session,
                                        db_index=True, 
                                        null=True,blank=True,
                                        primary_key=True)
        user = models.ForeignKey(User,null=True,blank=True)
        date = models.DateTimeField(
                       help_text=&quot;Date Request started processing&quot;,
                       auto_now_add=True,
                       db_index=True)
        processing_time = models.IntegerField(
                       help_text=&quot;Total time spent on this user&quot;)
        requests = models.IntegerField(
                       help_text=&quot;Total Requests in this session&quot;)
        stats = UserActivityManager()
        def __str__(self):
                return &#039;%s: %s %s - %s - %s&#039; % (self.user,self.session,self.date,self.processing_time,self.requests)
        class Admin:
                list_display= (&#039;user&#039;,&#039;session&#039;,&#039;date&#039;,&#039;processing_time&#039;,&#039;requests&#039;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
The nice thing about this set up is when we aggregate our activity logs we can pull out random stuff like total processing time for requests for a user/session, along with number of requests/user/session (and thus average request time)&lt;/p&gt;
&lt;p&gt;But that is just our model, we still need the magic. To implement the magic nicely I put some custom initial SQL into the sql directory of my application (in my case the housing application for this is called accounts, so I make a file called accounts/sql/useractivity.sql), you can read more about initial data &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#providing-initial-sql-data&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;http://www.djangoproject.com/documentation/models/fixtures/&quot;&gt;Django fixtures&lt;/a&gt;).My SQL looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE accounts_useractivity;
CREATE OR REPLACE VIEW accounts_useractivity AS 
SELECT i.session_id,
       i.user_id,
       MAX(i.date) as date,
       sum(i.request_time) AS processing_time, 
       count(*) AS requests 
FROM accounts_activitylog i 
GROUP BY 1 
ORDER BY NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So first I tell mysql to drop the table that django just created (accounts_useractivity), and create a view in it&#039;s place. The view is very simple, in that it just GROUP BY the session_id. The real hair puller for me was figuring out that I needed to use the MAX(i.date) (see more about &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html&quot;&gt;aggregate functions&lt;/a&gt;) to get the most recent access to float to the top when it normalises the data (otherwise the GROUP BY normally ORDER BY the session_id, which helps no one), the ORDER BY NULL is &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html&quot;&gt;an optimisation&lt;/a&gt; to tell GROUP BY not to ORDER BY. I am hoping that because date is an INDEX (from our logging model) it shouldn&#039;t cost too much to do a MAX. (I would like someone with Much MYSQL-fu to point out any further optimisations to this, or even alternative approaches to the whole thing).&lt;/p&gt;
&lt;p&gt;So now we have an aggregating VIEW which Django maps using it&#039;s ORM, so that to figure out sessions which have been active in the last x minutes (where x is a datetime.timedelta object) we simply do a:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UserActivity.objects.get_query_set().filter(date__gte=datetime.now()-x)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
I wrote a custom manager for getting recent sessions etc., but that is an exercise for the reader. What I did include in my model is something which returns a stepped &quot;request_weight&quot; i.e. session requests / largest session request x steps, which in my case defaults to 6. This means I can style my users like one would a &quot;&lt;a href=&quot;http://en.wikipedia.org/wiki/Tag_cloud&quot;&gt;tag cloud&lt;/a&gt;&quot;, so very active sessions will grow bigger than less active sessions. I needed to implement a helper function in the custom manager to return the session with the most requests.&lt;/p&gt;
&lt;p&gt;The final tip is to use a &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#subclassing-context-requestcontext&quot;&gt;context processor&lt;/a&gt; to make the information available to all your templates, although you could do it with middleware (maybe middleware is the proper way to do it?).&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/django">django</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/geek-tags/middleware">middleware</category>
 <category domain="http://www.whijo.net/geek-tags/mysql">mysql</category>
 <category domain="http://www.whijo.net/geek-tags/mysql-views">mysql views</category>
 <category domain="http://www.whijo.net/geek-tags/python">python</category>
 <category domain="http://www.whijo.net/geek-tags/statistics">statistics</category>
 <pubDate>Sun, 29 Jul 2007 21:52:25 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">110 at http://www.whijo.net</guid>
</item>
<item>
 <title>Growing up</title>
 <link>http://www.whijo.net/blog/amanda/2009/01/30/growing.html</link>
 <description>&lt;p&gt;Today something amazing happened that has never happened before. Finley climbed onto our bed, tucked himself in under the covers, read himself a book and then fell asleep All By Himself with the book left open on his chest.&lt;/p&gt;
&lt;p&gt;I was busy in the kitchen and was planning on going in to do all that for him, but he beat me to it! I think having had the experience of dealing with a challenging sleeper over the last two years (although the first year was so much more difficult than the second) makes this moment all the more sweeter. &lt;/p&gt;
&lt;p&gt;My little boy is growing up and as exciting as these new achievements are, there is still a little pang as I watch him not needing me for various things during the day. As he often reminds me &#039;I&#039;m a big boy now, mama.&#039;&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/amanda/2009/01/30/growing.html#comments</comments>
 <category domain="http://www.whijo.net/tags/cute">cute</category>
 <category domain="http://www.whijo.net/tags/finley">finley</category>
 <category domain="http://www.whijo.net/tags/sleep">sleep</category>
 <pubDate>Fri, 30 Jan 2009 16:00:15 +0200</pubDate>
 <dc:creator>amanda</dc:creator>
 <guid isPermaLink="false">464 at http://www.whijo.net</guid>
</item>
<item>
 <title>Statistics logging for Django - part 2</title>
 <link>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html</link>
 <description>&lt;p&gt;In &lt;a href=&quot;http://whijo.net/blog/brad/2007/07/19/statistics-logging-django.html&quot;&gt;part 1&lt;/a&gt; I explained how to build middleware and an associated model to capture page accesses, and tie them to a user session. Now that we have all this useful info logged we need to do something with it, like, display it. Unfortunately Django doesn&#039;t have a facility for using GROUP BY with mysql, so you have two major choices (there are more but we can ignore them): implement a custom request in a &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#managers&quot;&gt;custom Manager&lt;/a&gt; (see &lt;a href=&quot;http://www.djangosnippets.org/snippets/236/&quot;&gt;snippet&lt;/a&gt; and &lt;a href=&quot;http://www.djangosnippets.org/snippets/1/&quot;&gt;snippet&lt;/a&gt;, or &lt;a href=&quot;http://www.djangosnippets.org/tags/group-by/&quot;&gt;tagged snippets&lt;/a&gt;), or exploit a &lt;a href=&quot;&quot;&gt;mysql view&lt;/a&gt; and model it in Django. Now for me I prefer the latter because it means my custom sql becomes a mysql customisation and as far as Django is concerned it is dealing with a normal table (but don&#039;t tell Django that it is read only), and thus the model code works, so subsequent queries and manipulations can exploit the &lt;acronym title=&quot;Object Relational Manager&quot;&gt;ORM&lt;/acronym&gt; easily. My subjective and non-scientific experience is that using views is a lot more efficient/quick than using custom queries in the manager (it probably has to do with whatever optimisations exist with views, and the fact that you only fetch items when Django decides you need to fetch a row). So, how the hell do we do it?&lt;/p&gt;
&lt;p&gt;First I created a model that describes what information I want to deal with (something which maps neatly on to our other model):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UserActivity(models.Model):
        session = models.OneToOneField(Session,
                                        db_index=True, 
                                        null=True,blank=True,
                                        primary_key=True)
        user = models.ForeignKey(User,null=True,blank=True)
        date = models.DateTimeField(
                       help_text=&quot;Date Request started processing&quot;,
                       auto_now_add=True,
                       db_index=True)
        processing_time = models.IntegerField(
                       help_text=&quot;Total time spent on this user&quot;)
        requests = models.IntegerField(
                       help_text=&quot;Total Requests in this session&quot;)
        stats = UserActivityManager()
        def __str__(self):
                return &#039;%s: %s %s - %s - %s&#039; % (self.user,self.session,self.date,self.processing_time,self.requests)
        class Admin:
                list_display= (&#039;user&#039;,&#039;session&#039;,&#039;date&#039;,&#039;processing_time&#039;,&#039;requests&#039;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
The nice thing about this set up is when we aggregate our activity logs we can pull out random stuff like total processing time for requests for a user/session, along with number of requests/user/session (and thus average request time)&lt;/p&gt;
&lt;p&gt;But that is just our model, we still need the magic. To implement the magic nicely I put some custom initial SQL into the sql directory of my application (in my case the housing application for this is called accounts, so I make a file called accounts/sql/useractivity.sql), you can read more about initial data &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#providing-initial-sql-data&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;http://www.djangoproject.com/documentation/models/fixtures/&quot;&gt;Django fixtures&lt;/a&gt;).My SQL looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE accounts_useractivity;
CREATE OR REPLACE VIEW accounts_useractivity AS 
SELECT i.session_id,
       i.user_id,
       MAX(i.date) as date,
       sum(i.request_time) AS processing_time, 
       count(*) AS requests 
FROM accounts_activitylog i 
GROUP BY 1 
ORDER BY NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So first I tell mysql to drop the table that django just created (accounts_useractivity), and create a view in it&#039;s place. The view is very simple, in that it just GROUP BY the session_id. The real hair puller for me was figuring out that I needed to use the MAX(i.date) (see more about &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html&quot;&gt;aggregate functions&lt;/a&gt;) to get the most recent access to float to the top when it normalises the data (otherwise the GROUP BY normally ORDER BY the session_id, which helps no one), the ORDER BY NULL is &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html&quot;&gt;an optimisation&lt;/a&gt; to tell GROUP BY not to ORDER BY. I am hoping that because date is an INDEX (from our logging model) it shouldn&#039;t cost too much to do a MAX. (I would like someone with Much MYSQL-fu to point out any further optimisations to this, or even alternative approaches to the whole thing).&lt;/p&gt;
&lt;p&gt;So now we have an aggregating VIEW which Django maps using it&#039;s ORM, so that to figure out sessions which have been active in the last x minutes (where x is a datetime.timedelta object) we simply do a:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UserActivity.objects.get_query_set().filter(date__gte=datetime.now()-x)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
I wrote a custom manager for getting recent sessions etc., but that is an exercise for the reader. What I did include in my model is something which returns a stepped &quot;request_weight&quot; i.e. session requests / largest session request x steps, which in my case defaults to 6. This means I can style my users like one would a &quot;&lt;a href=&quot;http://en.wikipedia.org/wiki/Tag_cloud&quot;&gt;tag cloud&lt;/a&gt;&quot;, so very active sessions will grow bigger than less active sessions. I needed to implement a helper function in the custom manager to return the session with the most requests.&lt;/p&gt;
&lt;p&gt;The final tip is to use a &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#subclassing-context-requestcontext&quot;&gt;context processor&lt;/a&gt; to make the information available to all your templates, although you could do it with middleware (maybe middleware is the proper way to do it?).&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/django">django</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/geek-tags/middleware">middleware</category>
 <category domain="http://www.whijo.net/geek-tags/mysql">mysql</category>
 <category domain="http://www.whijo.net/geek-tags/mysql-views">mysql views</category>
 <category domain="http://www.whijo.net/geek-tags/python">python</category>
 <category domain="http://www.whijo.net/geek-tags/statistics">statistics</category>
 <pubDate>Sun, 29 Jul 2007 21:52:25 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">110 at http://www.whijo.net</guid>
</item>
<item>
 <title>Statistics logging for Django - part 2</title>
 <link>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html</link>
 <description>&lt;p&gt;In &lt;a href=&quot;http://whijo.net/blog/brad/2007/07/19/statistics-logging-django.html&quot;&gt;part 1&lt;/a&gt; I explained how to build middleware and an associated model to capture page accesses, and tie them to a user session. Now that we have all this useful info logged we need to do something with it, like, display it. Unfortunately Django doesn&#039;t have a facility for using GROUP BY with mysql, so you have two major choices (there are more but we can ignore them): implement a custom request in a &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#managers&quot;&gt;custom Manager&lt;/a&gt; (see &lt;a href=&quot;http://www.djangosnippets.org/snippets/236/&quot;&gt;snippet&lt;/a&gt; and &lt;a href=&quot;http://www.djangosnippets.org/snippets/1/&quot;&gt;snippet&lt;/a&gt;, or &lt;a href=&quot;http://www.djangosnippets.org/tags/group-by/&quot;&gt;tagged snippets&lt;/a&gt;), or exploit a &lt;a href=&quot;&quot;&gt;mysql view&lt;/a&gt; and model it in Django. Now for me I prefer the latter because it means my custom sql becomes a mysql customisation and as far as Django is concerned it is dealing with a normal table (but don&#039;t tell Django that it is read only), and thus the model code works, so subsequent queries and manipulations can exploit the &lt;acronym title=&quot;Object Relational Manager&quot;&gt;ORM&lt;/acronym&gt; easily. My subjective and non-scientific experience is that using views is a lot more efficient/quick than using custom queries in the manager (it probably has to do with whatever optimisations exist with views, and the fact that you only fetch items when Django decides you need to fetch a row). So, how the hell do we do it?&lt;/p&gt;
&lt;p&gt;First I created a model that describes what information I want to deal with (something which maps neatly on to our other model):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UserActivity(models.Model):
        session = models.OneToOneField(Session,
                                        db_index=True, 
                                        null=True,blank=True,
                                        primary_key=True)
        user = models.ForeignKey(User,null=True,blank=True)
        date = models.DateTimeField(
                       help_text=&quot;Date Request started processing&quot;,
                       auto_now_add=True,
                       db_index=True)
        processing_time = models.IntegerField(
                       help_text=&quot;Total time spent on this user&quot;)
        requests = models.IntegerField(
                       help_text=&quot;Total Requests in this session&quot;)
        stats = UserActivityManager()
        def __str__(self):
                return &#039;%s: %s %s - %s - %s&#039; % (self.user,self.session,self.date,self.processing_time,self.requests)
        class Admin:
                list_display= (&#039;user&#039;,&#039;session&#039;,&#039;date&#039;,&#039;processing_time&#039;,&#039;requests&#039;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
The nice thing about this set up is when we aggregate our activity logs we can pull out random stuff like total processing time for requests for a user/session, along with number of requests/user/session (and thus average request time)&lt;/p&gt;
&lt;p&gt;But that is just our model, we still need the magic. To implement the magic nicely I put some custom initial SQL into the sql directory of my application (in my case the housing application for this is called accounts, so I make a file called accounts/sql/useractivity.sql), you can read more about initial data &lt;a href=&quot;http://www.djangoproject.com/documentation/model-api/#providing-initial-sql-data&quot;&gt;here&lt;/a&gt;, &lt;a href=&quot;http://www.djangoproject.com/documentation/models/fixtures/&quot;&gt;Django fixtures&lt;/a&gt;).My SQL looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE accounts_useractivity;
CREATE OR REPLACE VIEW accounts_useractivity AS 
SELECT i.session_id,
       i.user_id,
       MAX(i.date) as date,
       sum(i.request_time) AS processing_time, 
       count(*) AS requests 
FROM accounts_activitylog i 
GROUP BY 1 
ORDER BY NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So first I tell mysql to drop the table that django just created (accounts_useractivity), and create a view in it&#039;s place. The view is very simple, in that it just GROUP BY the session_id. The real hair puller for me was figuring out that I needed to use the MAX(i.date) (see more about &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html&quot;&gt;aggregate functions&lt;/a&gt;) to get the most recent access to float to the top when it normalises the data (otherwise the GROUP BY normally ORDER BY the session_id, which helps no one), the ORDER BY NULL is &lt;a href=&quot;http://dev.mysql.com/doc/refman/5.0/en/group-by-optimization.html&quot;&gt;an optimisation&lt;/a&gt; to tell GROUP BY not to ORDER BY. I am hoping that because date is an INDEX (from our logging model) it shouldn&#039;t cost too much to do a MAX. (I would like someone with Much MYSQL-fu to point out any further optimisations to this, or even alternative approaches to the whole thing).&lt;/p&gt;
&lt;p&gt;So now we have an aggregating VIEW which Django maps using it&#039;s ORM, so that to figure out sessions which have been active in the last x minutes (where x is a datetime.timedelta object) we simply do a:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UserActivity.objects.get_query_set().filter(date__gte=datetime.now()-x)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;
I wrote a custom manager for getting recent sessions etc., but that is an exercise for the reader. What I did include in my model is something which returns a stepped &quot;request_weight&quot; i.e. session requests / largest session request x steps, which in my case defaults to 6. This means I can style my users like one would a &quot;&lt;a href=&quot;http://en.wikipedia.org/wiki/Tag_cloud&quot;&gt;tag cloud&lt;/a&gt;&quot;, so very active sessions will grow bigger than less active sessions. I needed to implement a helper function in the custom manager to return the session with the most requests.&lt;/p&gt;
&lt;p&gt;The final tip is to use a &lt;a href=&quot;http://www.djangoproject.com/documentation/templates_python/#subclassing-context-requestcontext&quot;&gt;context processor&lt;/a&gt; to make the information available to all your templates, although you could do it with middleware (maybe middleware is the proper way to do it?).&lt;/p&gt;
</description>
 <comments>http://www.whijo.net/blog/brad/2007/07/29/statistics-logging-django-part-2.html#comments</comments>
 <category domain="http://www.whijo.net/geek-tags/django">django</category>
 <category domain="http://www.whijo.net/tags/geek">geek</category>
 <category domain="http://www.whijo.net/geek-tags/middleware">middleware</category>
 <category domain="http://www.whijo.net/geek-tags/mysql">mysql</category>
 <category domain="http://www.whijo.net/geek-tags/mysql-views">mysql views</category>
 <category domain="http://www.whijo.net/geek-tags/python">python</category>
 <category domain="http://www.whijo.net/geek-tags/statistics">statistics</category>
 <pubDate>Sun, 29 Jul 2007 21:52:25 +0200</pubDate>
 <dc:creator>brad</dc:creator>
 <guid isPermaLink="false">110 at http://www.whijo.net</guid>
</item>
</channel>
</rss>

