Thursday, June 21, 2012

Open Source




If you want to deploy quickly with a small team of people, the only way to do that is to leverage open source as much as you can. Any work that results in more open source software being utilized, is work worth pursuing.


Software Developers in an agile process are not application developers, they are open source software implementers. There job is to identify places where open source can be implemented, and then do the work to implement that solution.


Agile software developers should only be writing algorithms if that algorithm has never been written before. Hint: most algorithms have already been written.


Sure, you can write it yourself and keep it closed source and charge others to use it (instead of releasing it for free), but then you wouldn’t get the benefit of having other peopel contribute bug fixes for you. I had my first pull request the other day for one of my github projects. It was a real eye opening experience to witness a bug magically fixed without me doing anything. How great it is to be an open source developer.

Why I stopped using virtualenv



I’ve decided to stop using virtualenv entirely. No more longer with local development, no longer with I deploy my stuff either. Why? Because virtualenv is evil. What is it’s purpose? To tie tour project to an environment frozen in time. When a new version of django is released, instead of updating your project to use the newest features, you end up telling yourself “Why bother with all that work, thats what virtualenv is for!” You’ve now just made  locked yourself into using that outdated version of django forever. Virtualenv encourages this sort of thing to happen.

I don’t use virtualenv at all. I install everything to global site-packages. On a practically daily basis, new features and implementations are being added to the open source oeuvre. On the rare event that a new version of django (or any other 3rd party I use) breaks my application, then and only then will I reach for virtualenv. Then I file a bug report, and then I wait for the bug to be fixed so I can get rid of virtualenv again.

I don’t hate virtualenv, I’m just weary of it. I don’t want to let it become a crutch. Thats why I don’t use virtualenvwrapper. I want my virtualenv experience to be painful.


A while ago I stopped using virtualenv when I deploy, but for completely different reasons. Its does not make sense in the day and ago of cloud computing to have two projects running on the same machine. Back in the shared hosting days, virtualenv was a must, but now we just spin up a new instance.

Sunday, March 7, 2010

Switch between dev server and production server with a handy bookmarklet

In firefox, right click on the bookmark toolbar. Then select "New Bookmark"

then enter this code into the "Location" textbox:

javascript:d1='example.com';d2='localhost';loc=window.location.toString();if(loc.match(d1)){window.location=loc.replace(d1,d2);}else%20if(loc.match(d2)){window.location=loc.replace(d2,d1);}

replace 'localhost' with your dev server domain, and replace 'example.com' with your production server domain.

When you click on this bookmarklet, it will replace 'example.com' with 'localhost' and/or vise versa. It is handy for switching back and forth between your site's development server and production server

Thursday, December 3, 2009

Django settings.py

Today I accidentally deleted a settings.py file for a project I was working on. I google'd like crazy to try to find a place where I can get a copy of the default settings.py file to start rebuilding the settings file. I could not find one, so I had to go through the trouble of starting a new app, and then extracting the settings.py file from it.

To all you people who find yourselves in the same predicament that I was in today, here is the whole settings.py file in it's full glory. It was generated by Django 1.1.1.


# Django settings for ff project.

DEBUG = True
TEMPLATE_DEBUG = DEBUG

ADMINS = (
# ('Your Name', 'your_email@domain.com'),
)

MANAGERS = ADMINS

DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = '' # Or path to database file if using sqlite3.
DATABASE_USER = '' # Not used with sqlite3.
DATABASE_PASSWORD = '' # Not used with sqlite3.
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.

# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/Chicago'

# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'

SITE_ID = 1

# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True

# Absolute path to the directory that holds media.
# Example: "/home/media/media.lawrence.com/"
MEDIA_ROOT = ''

# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''

# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash.
# Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'

# Make this unique, and don't share it with anybody.
SECRET_KEY = '&52lnelj34-*+76c2x*e0q4&b9zec%@^l_@s%*u*ar!%b5v!yr'

# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.load_template_source',
'django.template.loaders.app_directories.load_template_source',
# 'django.template.loaders.eggs.load_template_source',
)

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)

ROOT_URLCONF = 'ff.urls'

TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
)

Wednesday, November 11, 2009

Passwordless SSH made easy

Every six months, Ubuntu comes out with a new version. Each time that happens I always end up doing a fresh install, rather than taking the chances of an upgrade gone wrong. This means that every six months, I have to set up certain things all over again. The only thing I keep from one install to the next is my Firefox profile, and my "~/home/me/pictures" directory.

One of the things that always gets me is setting up SSH keys so I can log into my various shells without needing to type a password. So instead of this:

chris@chris-comp:~$ ssh chris@myshell.com
password for chris@myshell.com:
chris@myshell:~$


I can just do this:

chris@chris-comp:~$ ssh myshell
chris@myshell:~$


How to do it



First lets add a ssh "bookmark". Add the following to a file called "config" in your .ssh directory:

chris@chris-comp:~$ vim ~/.ssh/config
...


Host myshell
User chris
HostName myshell.com



This basically sets up an alias that lets you use "myshell" in place of "chris@myshell.com". This alias works anywhere on the system, including scp:

chris@chris-comp:~$ scp ~/myfile.txt myshell:~/



If you use Ubuntu, you can go to Places -> Connect to Server, then select "ssh" in the dropdown menu. Under server, just enter "myshell". This will allow you to browse the contents of that ssh machine in Nautilus! Pretty cool! Now you can drag and drop stuff from your ssh machine into apps on your desktop!

Creating the keys


Now, lets create our keys, run the ssh-keygen command:

chris@chris-comp:~$ ssh-keygen -t rsa -C "me@email.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/chris/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/chris/.ssh/id_rsa.
Your public key has been saved in /home/chris/.ssh/id_rsaub.
The key fingerprint is:
01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db me@email.com


This command basically creates both a private key and a public key for you, and places it in the /.ssh directory in your home directory. The idea is that you place the public key onto any server's you want to connect to. When you connect to that server, it will check the public key you gave it against your private key. If it matches, you're let in. Otherwise it'll ask you for a password.

Now that we've created the keys, lets add them to our server. Googling around the internet brings up a lot of ways to do this, but by far the easiest is this:

chris@chris-comp:~$ ssh-copy-id -i .ssh/id_rsa.pub myshell


This command will copy your public key over to the machine's public key holder place (.ssh/id_rsa.pub).

If all went well, you can connect to your ssh host without needing a password:

chris@chris-comp:~$ ssh myshell
chris@myshell:~$


Yay!

Saturday, October 17, 2009

"View on site" links in list view with Django Admin

The Django admin application has this feature where when you click on an object, if that Model has a "get_absolute_url" method defined, in the corner of the page there will be a link that will go to that object on your site:



This is really handle, but how can I get that link to be put in the list view, so I don't have to click on the object first to get to that link? The answer is to define the following function in the model:


def adminlink(self):
link = self.get_absolute_url()
url = "http://example.com%s" % link
link = "<a href='%s' target='_blank'>Link</a>" % url
return link
adminlink.allow_tags = True


Make sure you include the last line. You must set the function attribute "allow_tags" in order for the admin to not escape the '<' and '>' characters.

The admin declaration, should look like this:


class MyAdmin(admin.ModelAdmin):
list_display = ('user', 'field1', 'field2', MyModel.adminlink)

admin.site.register(MyModel, MyAdmin)


Now when you look at the admin page where it lists all object of MyModel, there should be a new column with your links in it:


Sunday, September 27, 2009

Deploying Django to replace a PHP site

From October 2007 to May 2008 I developed a webapp in PHP. Over the past year and a half since it's been up, I've added a few features to it, and it's slowly grown in popularity. A few months ago I came to the conclusion that the site need a total rewrite in order for the code to move forward.

After doing some research, I decided on Django, since just about everything I've read about the framework has been positive.

Fast forward to now. The Django version of the site is about 90% done. It uses way less code, and does everything the PHP site did and then some. Plus, it really only took me 2 months to do the Django version, whereas it took my about 6 or 7 to do it in PHP. Some of that has to do with Django's tools, but mostly the reduced time is attributed to me being a better programmer, as well as the overall design of the site already being determined.

Deploying

Now I must somehow figure out the best way to replace all the PHP code with the Django code. Tjis is not an easy task. The Django version of the site has a completely different authentication method than the PHP version. It's not as easy as jut migrating all data from the PHP database, to the Django database. (my PHP site uses MySQL and the Django version of my site uses PostGIS)

My Plan

This is what I have decided to do: Right now I have the old PHP version of the site located at domain.com, and the Django version located at beta.domain.com. I want to rename the PHP domain to old.domain.com, and then rename the Django url to plain ol' domain.com. Also I am going to have it so if someone tries to visit a link such as "domain.com/page.php", it will detect it's a link to the old site (due to the presence of ".php" in the URL), then redirect that request to "old.domain.com/page.php".

Mod_rewrite, right?

There are a few ways to do it. The first way that pops into the mind of a web developer is to create a new mod_rewrite rule. Mod_rewrite is a well-known apache module that handles rewriting URL's. I have never really gotten used to using mod_rewrite, mostly because theres no real way (that I know of) to test rules without breaking your site.

My Solution

After thinking about the problem for a few days, I came up with a pretty simple and convenient way to do the redirection all within Django. This is how:

First, point the following domains to your django app: domain.com, and beta.domain.com. Point old.domain.com to the PHP app. If you're using webfaction for your host (and you should if you're wanting to deploy a django app on a shared hosting budget), then this is really easy. Just go to the control panel and point your subdomains to the proper application profiles. If you're not using webfaction, then you'll have to mess around with your apache's httpd.conf settings.

Secondly, add a new app within your django project called "redirect". Inside that new app, create a new view called "redirect". That view should look like this:


from django.http import HttpResponseRedirect

def redirect(request):
"""redirect this request to the old domain
(the PHP version)
"""

return HttpResponseRedirect("http://old.domain.com" + request.get_full_path())


Basically what this does is redirect all requests to a new domain. We want to send all requests trying to get a ".php" file through this view. We achieve this by adding the following to urls.py:


(r'\.php', "redirect.views.redirect", ),


For best results, put this urlpattern at the very bottom of the list. This urlpattern matches any url that contains ".php" anywhere in it.

And thats it. I personally haven't deployed this method completely yet, as my Django site still has a few weeks of testing to go through before it's sent out to the public. Once I get it out there, I'll edit this post with any problems or workarounds I encountered.