Dustin Davis
Developer, Entrepreneur

My mom called me last night. She owns a hot tub dealership in the Salt Lake City area. She said she had a woman come in and purchase a top of the line hot tub the other day. The woman said, \”I\’m glad I found you. You were not on Google when I searched. Someone up the street said I should check out your store.\”

My mom has been paying some company $320 per month for the last nine months that promised to get her on the first page of Google. They have literally done nothing except continue to bill her month after month.

I don\’t know how effective an SEO campaign would be if you are only paying $320 per month, but I know I could take half that and start getting lots more exposure than she is getting just by using Google Adwords.

Basically my mom was calling to ask if she could send that money to me instead in return for me helping her get more traffic. I agreed to do it. I have enough experience that I put together a simple marketing plan for her so I can start to get her more customers and as I prove I can get a lot better ROI, then we can increase our marketing spending and efforts, and I can hire a virtual assistant to do the majority of the work.

But this post isn\’t really about marketing, SEO, or hiring VAs. My first order of business was to figure out how to allow my mom to set up an auto payment plan on her business credit card to put that $320 into my account each month.

Without doing much research I turned to stripe, because I know I could get it set up fairly quickly with their API. And it was relatively quick. It took me about 2 hours to do the research and create the page. It is nothing fancy.

First I created a folder on my server named payments and dropped the lib folder of the PHP Stripe API library into it.

I created a config.php file with the following:


$stripe = array(
  &quot;secret_key&quot;      =&gt; &quot;<strong>my_secret_key</strong>&quot;,
  &quot;publishable_key&quot; =&gt; &quot;<strong>my_publishable_key</strong>&quot;


I created a subscription plan on the Stripe admin page and gave it an ID of \”soakers320\”. Then I created this very simple index.php page:


if($_SERVER[&quot;HTTPS&quot;] != &quot;on&quot;)
    header(&quot;Location: https://&quot; . $_SERVER[&quot;HTTP_HOST&quot;] . $_SERVER[&quot;REQUEST_URI&quot;]);

if ($_SERVER[\'REQUEST_METHOD\'] == \'POST\') {
    require_once(dirname(<strong>FILE</strong>) . \'/config.php\');

<pre><code>$token  = $_POST[\'stripeToken\'];

$customer = Stripe_Customer::create(array(
    \'email\' =&amp;gt; \'customer@example.com\',
    \'card\'  =&amp;gt; $token,
    \'plan\'  =&amp;gt; \'soakers320\'

echo \'&amp;lt;h1&amp;gt;Successfully charged $320.00. Thank you!&amp;lt;/h1&amp;gt;\';


&lt;title&gt;Red Seam Marketing Subscription&lt;/title&gt;

&lt;?php if ($_SERVER[\'REQUEST_METHOD\'] == \'GET\'): ?&gt;

&lt;script src=&quot;https://checkout.stripe.com/v2/checkout.js&quot;&gt;&lt;/script&gt;
  &lt;script src=&quot;https://code.jquery.com/jquery-2.1.1.min.js&quot;&gt;&lt;/script&gt;

&lt;form method=&quot;post&quot;&gt;

      var token = function(res){
        var $input = $(\'&lt;input type=hidden name=stripeToken /&gt;\').val(res.id);

<pre><code>  StripeCheckout.open({
    key:         \'**my_publishable_key**\',
    address:     true,
    currency:    \'usd\',
    name:        \'Marketing\',
    description: \'Marketing Plan for Soakers.biz\',
    panelLabel:  \'Subscribe\',
    token:       token,
    allowRememberMe: false

  return false;

&lt;?php endif; ?&gt;


Some notes on the code:

Lines 3-7: I should also mention I purchased a $9.00 SSL certificate at namecheap.com for added security. This code ensures that the page is only accessed via https.

Lines 9-22: After the form is filled out and submitted, it creates a form past back to this page. This is the call that creates the customer using the subscription plan and actually charges their card.

Lines 41-59: This creates the Checkout form that generates the card token that is passed into this page when it is posted.

That\’s about it! Let me know if you have questions on any other portions of this code.


I set up my OpenSprinkler last night. It was a pretty painless endeavor. I\’ll walk through it in this post so I can explain what I did.

The Purchase

There are a few different models and options to choose from on RaysHobby.net. Time seems to be my greatest commodity at this stage in life so I opted to get the fully assembled model for $149.99. It seemed a bit expensive. I paid the same price for a 12 station remote control system at my old home a few years ago. But that system, while very nice, didn\’t allow me the customization I knew I would get with this system. Also, I didn\’t have to worry about my kids walking off with the remote. I could just use my phone or computer.

Additional Purchases

Once the system arrived, I realized I didn\’t have everything I needed to set up the sprinkler. Specifically, I needed a power source and something to get the internet to this thing.

Power Supply

The instructions said I needed a 24VAC transformer. Honestly I had no idea what this was. I did a little research and ended up buying this one on Amazon for $14.96. It has a plug for Rain Bird devices at the end of it. I just cut it off and trimmed the wires to attach to my power terminal block on the OpenSprinkler. I learned that each of this blocks pull out with makes it easy to attach wires.

In hindsight, I didn\’t really need to purchase this. When I took down my cheap sprinkler controller that the landscapers set up I noticed it uses a 24VAC transformer and I just had to loosen some screws to detach it and it would have been easier to use — no trimming of wires required.

Internet Connection

Unfortunately there is no wireless adapter built into this thing so I had to find a wireless bridge. I found this one on Amazon which I purchased for $25.99.

It was a pretty simple setup to get it configured:

  1. Plug in it.
  2. Connect to it as if it were your wireless rounder.
  3. Go to in your browser to access to admin config.
  4. Put it in repeater mode, connecting to you main wireless router.

That\’s basically it. Then I just ran a Cat5 cable (which came included in the package) to the OpenSprinkler.

Managing Open Sprinkler

Once it connects to your home network, if you push the top button on the right side of the device it will display it\’s IP address. In my case it was I then went to this IP in my browser ( and was propted to log in. The default password is printed on the OpenSprinkler instructions. From here you can change settings, set up stations and watering times etc.

Wiring up the sprinkler wires was also simple. I just moved the wires from my old system to the new OpenSprinkler. There wasn\’t much to it.

I downloaded the Sprinklers App on my iPhone. Because my iPhone was connected to my home network as well it was able to easily detect my OpenSpinkler automatically.

I installed the app on my wife\’s phone so now she doesn\’t have to ask me to turn on sprinklers on random occasions.


All in all this project cost me around $190 and about 2 hours of time. You could save some money if you went with a DIY kit and didn\’t need to buy the 24VAC transformer or wireless bridge. But I am sure going to love the ability to manage my sprinklers on a web page rather than a tiny black & white display. Also I have the ability to control my sprinkler from anywhere. (I could put it on a public IP but I haven\’t done this yet since I work from home and don\’t really see the need to control my sprinklers from out of town. But who knows, that could change.)

Last week as I was driving to a user group meeting, I listed to a podcast episode. The subject was managing email. Since then I have cleaned up my inbox and I have been at inbox zero or close to it all week. Between my 5 email accounts I have 1 message in my inbox right now. I\’m used to having thousands of emails in my inbox, half of them unread.

One thing that has helped is that I have determined that my inbox is a terrible todo list. I have started using Wunderlist more to manage my daily tasks.

Generally I’ll start my morning by looking through my todo list in Wunderlist. I’ll set due dates on all the items I want to get done today and set the due date as today. Wunderlist has an automatic category named Today where I can see all the tasks I have set as due today.

I generally try to have no more than 10 tasks. Some are long tasks, some are simple. My goal is to get through the task list for the day. I work on one until I check it off, then I work on another I generally get about 5-6 task done in a day leaving 4-5 left that are still in my today list the next day because they are now marked as overdue. I generally just mark them as due today, or leave them as overdue and because they show up in red they tend to get a higher priority.

So as I go through my emails, if there is something that I need to take action on, I move it into my todo list. Basically, the only thing that stays in my inbox is a message that I need to reply to and I can’t just reply quickly to. Either it needs some thought or action first. But, if it needs some action first, I will generally put it in my todo list and then find the email later and reply after I complete the todo task.

For email I have been using a combination of Airmail, Gmail, and I just got the beta of Mailbox for Mac, which have been using on my iPhone and I really like it. Each client has it’s pros & cons, but I think I\’ll save that topic for another day.

Last night I listed to a talk from Nathan Barry from MicroConf 2014. It was a good reminder of the power of habits and doing things on a daily basis. He actually wrote an iPhone app to track your habit goals and your streak. I looked at it and ultimately decided on a different one named Habit List. Today is my first day using it but so far I really like the flexible features. For example, some habit I may only want to do on Mon, Wed, Fri. Some habits I want to do 5 days a week, but it doesn\’t matter which days. Habit List supports these features. I just really wish they had a desktop app to go with it like Wunderlist.

Hopefully I can develop some good habits and report back later that the productivity apps and processes are still working.

The pluralize filter is great for humanizing text in templates. But what if you want to use the same function inside of your python code. For example, let\’s say you are sending back a message via ajax.

It is simple enough to make use of the same pluralize filter function in your python code:

from django.template.defaultfilters import pluralize

def message(count):
    return \'{} item{} {} updated.\'.format(
        count, pluralize(count), pluralize(count, \'was,were\'))

Testing this would produce the following results:

In [3]: message(1)
Out[3]: \'1 item was updated.\'

In [4]: message(2)
Out[4]: \'2 items were updated.\'

In [5]: message(0)
Out[5]: \'0 items were updated.\'

We just moved into a new house about a month ago. This is actually the second time we have built. I had forgot how much work a new home is. There is so much to be done. One big project we need done is landscaping the backyard. 

Since we have pretty much run out of money with all our other upgrades, I decided that rather than go into debt to get it done, I could perhaps find a landscaper willing to do some trade work. I could build a website for their business in return for their landscaping services.

I’ve actually done trade work once in the past. I needed my house painted and posted an ad on Craigslist. I had an artist come and paint my house and I built a custom website for him in return to feature his artwork. I think after all the iterations and custom work I did, he came out far ahead.

But, I really need a backyard, so I wrote up a few on craigslist. I got a couple of bites. The first one was someone who wanted me to call them. I emailed them instead and never heard back from them. If I can’t communicate with someone via email, I don’t think we will be able to work well together.

The second person who contacted me was all about every plant being editable and water-wise. She said if I liked to use bug spray and non-organic fertilizer then she didn’t feel we would be a good fit. I didn’t feel we would be a good fit.

So I figured it was time to just find someone and get a bid. I subscribed to Angie’s list and sent emails to about 6 different landscapers. I didn’t hear from any of them. It must be a busy time of year. My wife called a few places and actually got one person to return her call. 

But then something crazy happened. Some neighbors came to visit and welcome us into the area. When I told them what I did for a living, one of them’s eyes lit up and said “We need to talk.” I actually hear this a lot. Since I haven’t really done freelance work in many years I usually just let them know I’m not in the freelance business. But, when he said he owned a landscaping company and needed a website and that he was interested in trade work, my eyes lit up. “Yes, we need to talk!\”

So we are going to work with each other. Hopefully it all goes well. My wife tends to get nervous when I do business with neighbors because she is afraid that something may go bad and cause hurt feelings and awkward relationships. I tend to look at it as more of a blessing. I love trade work. There is something appealing for about trading skills for skills and work for work.

So what do you think? Have you ever done trade work? How did it go? Would you do it again?

Recently Luke lamented that his favorite ebook reader was being sunset. I suggested Kindle. I was showing off the features I liked about Kindle when I realized that while I could send personal documents (PDF, MOBI) to my kindle devices (Kindle, iPad, iPhone), these personal documents were not showing up in Kindle for Mac or Kindle Cloud Reader. These apps only let me read kindle books I have purchased on Amazon.

This morning I got the following email from Amazon:

Dear Kindle Customer,

As a past user of personal documents on Kindle devices or reading apps, we are pleased to let you know about some improvements we\’ve made to how personal documents work.

Personal documents are now in Amazon Cloud Drive: Starting today, all personal documents that you have archived in your Kindle Library will be available to access, delete, organize, and share from your Amazon Cloud Drive. You can see these documents in a new \”My Send-to-Kindle Docs\” folder alongside all of your saved content such as photos and personal videos.

There is no action required on your part. Your personal documents features will continue to work just as they have in the past. And as always, you can use Manage Your Kindle to see a list of your documents, re-deliver them to Kindle devices and free reading apps, delete them, or turn off auto-saving of documents to the cloud. Documents will be delivered just as they have in the past and you will continue to have 5 GB of free cloud storage for your personal documents. Just \”Send Once, Read Everywhere.\”

Documents stored in their native format: Also starting today, new documents that you save to the cloud with Send to Kindle will be stored in their native format (e.g. MS Word, TXT) so you can access them anywhere from Amazon Cloud Drive.

Please note: Your usage of Amazon Cloud Drive is subject to the Amazon Cloud Drive Terms of Use.

The Docs Team

To learn more about sending documents, news, blogs, and other web articles to your Kindle, please visit amazon.com/sendtokindle

To learn more about Amazon Cloud Drive features and apps, please visit amazon.com/clouddrive/learnmore

I learned a number of things from this email.

First, I didn\’t even know there was an Amazon Cloud Drive app that essentially works like Dropbox, Copy, Google Drive, etc. So I installed it. What\’s one more?

After installing it, I had a cloud drive folder and it synced some MP3\’s I had purchased from Amazon in the past. It also created a folder that contained all my personal docs I had syned to my kindle devices.

Still, I didn\’t see a way to automatically see these docs in my Kindle for Mac app.

Fortunately, I found an easy hack to make it work.

How to sync personal kindle docs with your local kindle app

In the Kindle for Mac app, you can set your content folder in your preferences. Just set this to the Amazon Cloud Drive folder that contains all your personal Kindle docs. Done!

Still, I don\’t think it will sync furthest location read between devices. Hopefully this is addressed at some point.

Sending personal docs to kindle

The other thing I learned is that there are easier ways to send personal documents to kindle. While the email message is nice, it is not ideal. I downloaded the Send To Kindle app that lets you simply drag & drop a file. It will even try to convert PDF files to Kindle files. I\’m not sure how well it works though.

Good job Kindle & Docs teams. Keep up the good work!

Show Your WorkMy junior year of high school I took a calculus class from the local college extension. I happened to get a nice Casio calculator (circa 1994) that allowed me to write programs. Since I didn’t really have a computer, this was my first experience programming.

Each time we learned a new algorithm, I would figure out how to program that algorithm into my calculator. The problem was that once I did, I would promptly forget how to work the problem by hand.

I mostly got B’s on my tests. I would get all the answers right, but I would often get docked for not showing my work. How do you show your work when the only way you remember how to solve a problem is to plug numbers in to the program you wrote on your calculator?

I was thinking about this recently. Today I might be accused of the opposite. When I leave a comment in a bug or feature ticket, I might be accused of sharing too much information. The business person that submitted the bug in our ticket system doesn’t really need to see all the queries I ran, the results of those queries and a documented process of how I came to find my solution. They just wanted it fixed.

But here’s the deal: I don’t write all that information for them. I write it for me.

Like the algorithms I programmed into my calculator in high school, I will promptly forget what I worked on two days ago and the process I followed. If I run into a similar problem, finding the ticket where I documented my process of finding a solution in the past is often much faster than coming up with that solution (or a new solution) again. This is also why I blog.

So, if you are going to err, err on the side of sharing too much information. Even if nobody cares, you will thank yourself later. On top of that, the business person requesting the bug fix might just learn a little more about your system in the process.

Lately I was getting this error frequently as I was using Django’s built in cache_page decorator to cache some views.

memcache in check_key
MemcachedKeyLengthError: Key length is > 250

Basically the problem is that Memcached only allows a 250 char key and some of my view names were pretty long and so it was creating keys greater than 250 chars.

I found a quick fix to hash the key with an md5 hash if the key is going to be over 250 characters. You can modify the function that creates the key.

In my settings file I added the following:

import hashlib


def hash_key(key, key_prefix, version):
    new_key = ':'.join([key_prefix, str(version), key])
    if len(new_key) > 250:
        m = hashlib.md5()
        new_key = m.hexdigest()
    return new_key

    'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': '',
        'KEY_FUNCTION': hash_key,

The reason why I only hash if the key is going to be over 250 characters is because 1) hashing is CPU intensive and I only want to do it when I have to; 2) I prefer to have my memcached keys human readable when possible; 3) less likely to have collision problems with duplicate hashes.

I thank Russell Keith-Magee for these tips.

Even after coding in Python for the past five years I’ve never really considered myself an expert in the language because I find the more I know, the more I know I don’t know. I generally keep my code simple on purpose until I have a good reason to be complex – which for most django sites, I haven’t had a good reason to be complex.

Today I had good reason. I’m currently building a number of key performance indicator (KPI) stats for Neutron. There are currently 46 different stats that I need to calculate for 5 different time periods.

For each state I need:

  • Stats for start of current day to current time with a comparison to yesterday start of day to the current time.
  • This week compared to last week delta
  • This month compared to last month delta
  • This quarter compare to last quarter delta
  • This year compared to last year delta

I will be building a view for each stat and associated time period to return these values in JSON format. So as it stand there will be 230 views. I needed to come up with something clever to save myself some lines of code. I opted for class based views.

First I built a base class that will return the JSON data in a consistent format:

class StatWithDelta(BaseDetailView):
    start = None
    end = None
    delta_start = None
    delta_end = None
    title = None
    subtitle = None

    def __init__(self):
        super(StatWithDelta, self).__init__()
        self.end = djtz.localtime(djtz.now())

    def value(self):
        raise NotImplementedError

    def delta(self):
        raise NotImplementedError

    def get(self, request, *args, **kwargs):
        value = self.value()
        delta_value = self.delta()
            delta_percent = round((((delta_value - value) / value) * 100), 2)
        except ZeroDivisionError:
            delta_percent = 0
        payload = {
            'value': value,
            'delta': delta_percent,
            'title': self.title,
            'subtitle': self.subtitle,
        return self.render_to_response(payload)

    def render_to_response(self, context):
        return self.get_json_response(self.convert_context_to_json(context))

    def get_json_response(self, content, **httpresponse_kwargs):
        return http.HttpResponse(content,

    def convert_context_to_json(self, context):
        return json.dumps(context)

Next I built classes for each required time range. Here is my class for today compared to yesterday:

class TodayYesterday(StatWithDelta):
    subtitle = 'Today vs. Yesterday'

    def __init__(self):
        super(TodayYesterday, self).__init__()
        self.start = self.end.replace(hour=0, minute=0, second=0, microsecond=0)
        self.delta_start = self.start - datetime.timedelta(days=1)
        self.delta_end = self.end - datetime.timedelta(days=1)

Now for each stat I create a class that gets the main value and its delta value. Here is one example:

class GrossMarginPercent(StatWithDelta):
    title = 'Gross Margin Percent'

    def value(self):
        return functions.gross_margin_percent_within(self.start, self.end)

    def delta(self):
        return functions.gross_margin_percent_within(
            self.delta_start, self.delta_end)

I thought this was clever, but then I found myself writing a lot of similar code. I would create a class based view for each stat class and time period, then an associated url mapping. So for the stat class above I would have these five classes:

class GrossMarginPercentDay(GrossMarginPercent, TodayYesterday):

class GrossMarginPercentWeek(GrossMarginPercent, ThisWeekLastWeek):

class GrossMarginPercentMonth(GrossMarginPercent, ThisMonthLastMonth):

class GrossMarginPercentQuarter(GrossMarginPercent, ThisQuarterLastQuarter):

class GrossMarginPercentYear(GrossMarginPercent, ThisYearLastYear):

… and these urls:

    url(r'^edu/gmp-dtd/$', GrossMarginPercentDay.as_view()),
    url(r'^edu/gmp-wtd/$', GrossMarginPercentWeek.as_view()),
    url(r'^edu/gmp-mtd/$', GrossMarginPercentMonth.as_view()),
    url(r'^edu/gmp-qtd/$', GrossMarginPercentQuarter.as_view()),
    url(r'^edu/gmp-ytd/$', GrossMarginPercentYear.as_view()),

You can see the lines of code adding up. I was going to add 230+ lines of code to my urls.py file and 4600 lines of code to my views.py file (20 * 230) following PEP8 guidelines.

So I decided to use one url pattern to send to one view function to dynamically create each of the stat-period classes. Here is my new url pattern:

        r'(?P<base_class_name>\w+)/$', 'magic_view'),

And here is my “magic_view” function that where the *magic* happens:

def magic_view(request, category, period, base_class_name):
    Builds a dynamic class subclassing the base class name passed in and a time 
    period class. It will return its as_view() method.

    URL structure: /category/period/KPI_Class/

    category: KPI category (edu, conversion, etc.) not really used at this point
    period: day, week, month, quarter, year
    KPI Class: One of the class names in this file
    class_name = '{}{}'.format(base_class_name, period.capitalize())
    _module = sys.modules[__name__]
    base_cls = getattr(_module, base_class_name)
    if period == 'day':
        period_name = 'TodayYesterday'
        period_name = 'This{0}Last{0}'.format(period.capitalize())
    period_cls = getattr(_module, period_name)
    # Create a dynamic class based on the base class and time period class
    cls = type(class_name, (base_cls, period_cls), dict())
    return cls.as_view()(request)

So if you include all the comments lines to explain why I did, I’m only using 25 lines of code to save 4830 lines. That’s a lot of typing. Python, my fingers thank you!

A friend pointed me to this simple yet humorous website yesterday which essentially gives a new lazy coder excuse whenever the page is refreshed.

I couldn’t help but whip out a bot to plug in to our IRC channel. My lazy coder bot will give a random excuse whenever someone mentions the word “why”.

I used my Rollbot script as a base to write this up quickly.




from bs4 import BeautifulSoup
import requests
from twisted.words.protocols import irc
from twisted.internet import protocol, reactor

NICK = '_lazy_coder_'
CHANNEL = '#yourchannel'
PASSWORD = 'channel_password'

class MyBot(irc.IRCClient):
    def _get_nickname(self):
        return self.factory.nickname
    nickname = property(_get_nickname)

    def signedOn(self):
        print "Signed on as {}.".format(self.nickname)

    def joined(self, channel):
        print "Joined %s." % channel

    def privmsg(self, user, channel, msg):
        Whenever someone says "why" give a lazy programmer response
        if 'why' in msg.lower():
            # get lazy response
            because = self._get_because()

            # post message
            self.msg(CHANNEL, because)

    def _get_because(self):
        req = requests.get('http://developerexcuses.com/')
        soup = BeautifulSoup(req.text)
        elem = soup.find('a')
        return elem.text.encode('ascii', 'ignore')

class MyBotFactory(protocol.ClientFactory):
    protocol = MyBot

    def __init__(self, channel, nickname=NICK):
        self.channel = channel
        self.nickname = nickname

    def clientConnectionLost(self, connector, reason):
        print "Lost connection (%s), reconnecting." % reason

    def clientConnectionFailed(self, connector, reason):
        print "Could not connect: %s" % reason

if __name__ == "__main__":
    channel = CHANNEL
    if PASSWORD:
        channel += ' {}'.format(PASSWORD)
    reactor.connectTCP('irc.freenode.net', 6667, MyBotFactory(channel))

*UPDATE: I’ve made some minor modifications and posted the project on Github