Documenting projects is hard, hosting them shouldn't be. Read the Docs was created to make hosting documentation simple. I think that we have solved this problem well, but now we need to start thinking about the larger picture.
Along with hosting, Read the Docs was created with 2 other main goals. One was to encourage people to write documentation, by removing the barrier of entry of hosting. The other was to create a central platform for people to find documentation. Having a shared platform for all documentation allows for innovation at the platform level, allowing work to be done once and benefit everyone. Having run the site for over a year now, I think there is a third thing that we should be striving for. That is to make the quality of documentation better.
I think that we can help a documentation culture flourish within the open source world. Django is a shining example of what a project with great documentation can do, and it has a community that values docs more than the norm. I think we can help spread this culture throughout the Python world, and beyond. This has already started, and I want to think about how something like RTD can help.
I think that having a guide for writing useful documentation would be a great step towards helping people along the path of documentation enlightenment. Jacob Kaplan-Moss has started down this road with his blog series and Pycon 2011 talk on this subject. I think that we could start by collecting these into a section of the site.
We could build on top of that great start with simple guides for how to get started with Sphinx, best practices for documentation, and providing a general place to learn more about how to write good documentation. Since we host a lot of documentation, we could point to live examples of techniques, and provide helpers for people to enable the techniques.
I have started a reStructedText Philosophy document that is meant to help people understand the ideas behind how reST works, so that it isn't as mystifying. This reST cheatsteet also appears to have similar goals. These are a very basic start, and I think some more along these lines would really help a lot of people get over the barrier to starting and continuing to write good documentation.
I think that we could also help create contributors to projects, if we could find an easy way to provide patches to documentation. If you could go to the project documentation, and fix small typos, or help add a paragraph in the tutorial, it would lower the bar to helping.
However, it isn't a wiki. These changes would be represented to the project author as pull requests in their VCS, and they would still be responsible for tending the garden. This gets rid of the "Just Edit The Wiki" solution of documentation, and also helps new contributors get started in an easier fashion.
The Plone community has built a proof of concept, linking to Github's edit pages for the current document. I think we can integrate this at the platform level, and make it available to everyone.
Read the Docs is open source. You can help by writing docs for the site, writing code for the site, or just writing documentation in general. People can also help just by using the site, and reporting bugs. Telling us how to make the site better helps everyone in the long run. Come join us on Freenode in the #readthedocs channel as well.
Another area that we're hurting is in the design front. We have been adding features over time, and the design of the site is getting a bit strained. Having someone with a good sense of design help re-think and re-architect some of the features and ideas that we've been working on I think would help a lot.
A lot of the RTD contributors will be at Pycon 2012, where we will be having a sprint on the site. If you want to get started contributing, that is a great place to come and get started.
CommentsIt's been a while since I last talked about Read the Docs, and there has been a lot of activity. This is an update on the latest and greatest new features.
The biggest news that has happened is that we have been given a grant from the Python Software Foundation to help host the site. Thanks PSF! They have blogged about it, and I am grateful that they have given us support. With the funds they have offered, we have been able to make Read the Docs a lot faster, and more robust. I will outline some of the changes below.
This also means we won't be going away any time soon!
We have a fancy new theme for documentation on Read the Docs! If you have the 'default' theme for your project, it will show up on the build of your docs on the site. I think it is pretty great, thanks to the designers who spent their time making it awesome. A really great feature is that the new theme is mobile ready. Go ahead and view a project using it on your phone, or make your browser smaller and you will see the fanciness. Having a custom theme will give us a base to build lots of other neat features on top of.
We had some connectivity trouble in between our servers a while back, and this prompted me to make the site respond better to these conditions. Every time you view documentation on a subdomain of readthedocs.org, your request will never hit Django. So all of these requests will work without a database. We have also added a second application server with a load balance in front, which means that one of the app servers could go away and your documentation would still get served.
That leaves our load balancer as the main single point of failure at the moment. We're using Varnish for the load balancer, and we've implemented strong caching of data. Varnish will cache your docs for up to a week, and it will be actively purged when you rebuild your docs. This means that your docs will usually be served out of memory, and without dependence on any other server but that one. We have plans to elininate Varnish as a single POF, and then it would only be our hosting provider that would be a single point of failure (famous last words).
Intersphinx is an awesome feature of Sphinx that allows you to reference remote sphinx documentation easily. RTD now supports it for every project that we host.
I've always had big ideas for rtfd.org, since it can act as a short-url for things. projectname.rtfd.org has always redirects to the projects docs, but now we have something a lot better. Inspired by Jacob Kaplan-Moss and his work on django.me, we now support human-edited deep-linking within documentation hosted on RTD.
Taking another page from Jacob's book, we seeded the index of our projects with their Intersphinx data, so a lot of references will automatically work. This works best with API reference docs, but anything people have put links to in their documentation should have been picked up. A couple of examples:
If you go to a non-existent link on rtfd.org, you will be prompted to enter a suggested URL. This will help build the data, and make it more useful for everyone.
RTD has had an API for a while now, and with the addition of the support for rtfd.org, I thought it would be neat to make it easier to access docs from the command line. With a simple pip install rtd, you will get an rtd utility that will open docs on RTD. It supports 2 arguments, the first being a project name, and the second being a slug to append for the rtfd.org functionality. So like the example above:
-> rtd pip
Pip Installs Packages.
Opening browser to http://pip.rtfd.org/
-> rtd celery Task
Distributed task queue
Opening browser to http://celery.rtfd.org/Task
It hits the RTD API to see if the project is on the site, and only opens your browser if it doesn't exist. I hope that in the future we'll make it easy to upload a project from the shell, and more.
Since we are a documentation site, we've always had documentation. I've been adding more as time has gone on, and most of the features I'll be talking about today are already documented. I also broke the documentation up into sections for users of the site, and developers on the codebase, so it should be easier to find for everyone to find what they are looking for.
I think that RTD can be doing a lot more to help out the community with regards to documentation. I'll write another post about that soon. But if you are interested in helping out with the effort, all of the code is open source and we love people to contribute. Feel free to jump in #readthedocs on Freenode as well, if you have any questions or thoughts.
CommentsReviewboard is a great tool for managing the process of Code Reviews. It has pretty good git support, but it might not be obvious what the best way is to use it. At work, I have a couple of different ways of pushing up code for reviews, which I'll talk about.
This guide is assuming you are using your own bare repositories, on the server hosting the Reviewboard instance. It's mainly here so that I can remember how to do this next time I need to. Also, thanks to Travis Cline for the initial pointers for this post.
Once you have Reviewboard installed, you need to add a Repository in the admin, which is located at /admin/db/scmtools/repository/. The required fields have the following values:
The Repository Documentation has more about why you need this screwy configuration.
Before we get started, you're going to want to get the post-review tool that works along with Reviewboard. The easiest way to get it is to pip install RBTools.
The easiest way to make sure your pointing at the right Reviewboard instance is the .reviewboardrc file in your home directory. You only need to add one line to that file, which is:
REVIEWBOARD_URL = "https://path.to.your.instance"
If you are working with multiple instances that map to different repos, you can set the Reviewboard instance for the specific repo as well:
git config reviewboard.url https://path.to.your.instance
Alright, now you are all setup to start posting reviews. The easiest way to do that is to branch off of master, and start committing. If you are following something similar to this awesome branching model, this should be your normal workflow. Once your branch is ready to be merged back into your project, you want to get it reviewed. You can send a review equivalent to "this branch diffed against master" like so:
post-review --guess-summary --guess-description
Another thing I find myself doing a lot is working on my master, and only having one commit to review. In theory this should probably be done on a bugfix branch, but such is life. There are other good use cases for only reviewing the latest commit as well. It's done like so:
post-review --guess-summary --guess-description --parent=HEAD^
It's also possible to review any number of previous commits. It looks a lot like the previous command:
post-review -o --guess-summary --guess-description --parent=HEAD~4 #To review last 4 commits.
If you are familiar with git, you will understand that there is a lot more that you can do with the --parent argument. I'll leave the possibilities up to your imagination.
The are a couple of other useful post-review flags, that I use from time to time.
I hope this makes it a little easier for you to set up a git repository with reviewboard.
CommentsDocumentation writing will always be hard work. It's a much different mind-set than programming, and people that write good code might not necessarily write good docs. However, this is a known issue, and something that can't really be solved.
What you can do is make it easier to write documentation. Every step along the way that you can give yourself an excuse to not write documentation is another undocumented open source project.
Luke Plant has a great post up about how important documentation is, and I completely agree. I imagine a lot of the people using Django are using it because of the documentation. I think as members of the Django community, we need to build a culture of documentation within the greater Python world. Python does tend to have better documentation than a lot of languages, but it's still not nearly what it could be.
Read the Docs exists to make it easier to host your Sphinx documentation. Over the weekend, Bobby, Jonas, and I added a bunch of new features to the site. I think it's getting to the point where there isn't an easier or better way to host the documentation for your Django project, and we're only going to keep improving it!
A different Eric added a really nice Getting Started guide for RTD, that shows how easy it is to get your projects hosted with us.
Anyway, on to the new features that we added.
Versions of projects are easily one of the biggest requested features on the site. For a long time we just supported building the latest versions of your documentation. Now we support versions of your documentation that are tagged in your VCS (hg/git only).
A lot of larger projects need versioning because they support one or two versions, as well as developing in the trunk. Django was the main project we were thinking of, but some other projects have put this to good use. A couple of examples are:
Sphinx has interesting support for PDF generation through Latex. In my testing it was pretty unreliable, but I was able to rangle it into working well enough to expose in the UI. So now almost every project will have a "Download PDF" button. This code has version support as well, so we can offer PDFs of certain versions.
Another interesting part of this feature is that this building code has been abstracted out, so we can support epub, plain text, and all the other Sphinx output formats that people want.
We killed the RTD header on hosted documentation pages in favor of a Badge in the lower right hand corner. The header clashed with a lot of the themes, and the badge is nice because it gives us a place to put functionality that is always visible, but is obviously not part of the hosted documentation. We want to build some more functionality into the badge, like switching between versions and linking back to the project's RTD page, once we build a good UI for it.
Revsys has agreed to sponsor the hosting costs for RTD. Jacob Kaplan-Moss has always been a big proponent of documentation, and I'm glad that he and Frank Wiles are helping us keep Read the Docs around and get better. We tried to make the sponsorship subtle and not intrusive, so please let me know if it bothers you and we can try and figure something out.
I think that these features are really starting to make RTD a compelling platform for hosting your documentation. We are planning more awesome features that will make RTD even better. I'm really excited about the project and I hope that you either host your docs with us, or find docs that we host useful.
CommentsI have seen a lot of talk over the past couple years about how to handle different settings files and databases, synced between production and development. I have happened onto a way of doing it that makes me happy, and figured I would share it with the world.
I use a file structure that looks like this:
project/
settings/
__init__.py (empty)
base.py
sqlite.py
postgres.py
The base.py contains all of the configuration options that are shared among the databases. INSTALLED_APPS, etc. All of the DATABASE settings should be specified in the more-specific files. As well as things that differ by environment, like remote servers, cache settings, cookie domains, and other things.
This allows you to run the sqlite settings file, and have it be set to localhost, or whatever your development settings are. Then in production you just run against the postgres settings.
A good example of this being used in practice is on Read the Docs.
But wait, there's more!
./manage.py is great for development. It is the easiest way to get started, and it automatically sets up your paths and stuff. With my setup, I actually explicitly set manage.py's settings file to the sqlite file.
This means that whenever you are using manage.py, you are in a development context. So, what do you do about production?
In production, you set your DJANGO_SETTINGS_MODULE to the postgres settings file. So whenever you use django-admin.py, you will be running against the production database.
I really like this scheme, because it gives you a logical distinction between production and development in your code, and in your interface on the CLI. When you are developing, you are using manage.py and editing the sqlite settings file. The reverse for production.
CommentsAt work, we have a wiki page that's called Required Reading. Named after that oh-so-lovely tradition in high school or college of having books that you needed to read over the summer before you started class. The idea being that they are relics of the culture of the company, and if you read everything, you will understand a lot about how work (and play) is done.
I think this is something useful that most companies should have. It was basically split into two parts: Silly and Serious. The silly parts are so that you can understand all of the inside jokes and references that people are bound to make. The serious parts are more philosophy and thoughts behind how we write code and do things.
Being a python shop, this Youtube Monty Python playlist is a must watch. It's got most of the famous Python gags, and being knowledgeable about the languages namesake makes you a more rounded human being. Speaking of namesakes, Django Reinhardt is a great jazz player, so I recommend you learn about him as well. He was quite the bad ass.
After that, there are just some of the classic online videos that everyone should watch:
and such.
The serious posts are a little more relevant, because they are more about the philosophy of how to code. I have been looking through a lot of really great posts on this subject lately, and these are some of my favorites:
Then there are some of the more topical guides that give a good knowledge and understanding of some of the fundamental things that you do at work. The unit testing guide above is a good example, but there are a few as well:
I think that having a guide to the culture of the company is really useful for people that are getting started. Plus I think it's a good way to remind people of what the values are of your team. Hopefully silliness is valued as much as good work, and you have a place to go when you want to see silly bits of the past.
I'd love to hear if other people have other ways of introducing culture to their companies. I'd also love to see some other really good topical guides on things that you may love, as I'm sure there are tons more out there.
CommentsOver on ReadTheDocs, I wanted to build search around the documentation that we're hosting. I chose Haystack and Solr for this, because it's the best way to do search in Django these days. However, I've only ever used Haystack to index content that is in the database. I thought about trying to add all the rendered HTML from the documentation into the database, but that was a non-starter.
I ended up adding a ImportedFile model to the database, which would contain the metadata for the HTML file:
class ImportedFile(models.Model):
project = models.ForeignKey(Project, related_name='imported_files')
name = models.CharField(max_length=255)
slug = models.SlugField()
path = models.CharField(max_length=255)
md5 = models.CharField(max_length=255)
This allows me to link the SearchIndex in haystack to a model. Then the interesting part is in the Haystack SearchIndex, where I override the prepare_text method, allowing me to read the data in from the filesystem instead of from the database.
class ImportedFileIndex(SearchIndex):
text = CharField(document=True)
author = CharField(model_attr='project__user')
project = CharField(model_attr='project__name')
title = CharField(model_attr='name')
def prepare_text(self, obj):
full_path = obj.project.full_html_path
to_read = os.path.join(full_path, obj.path.lstrip('/'))
try:
content = codecs.open(to_read, encoding="utf-8", mode='r').read()
return content
except IOError:
print "%s not found" % full_path
site.register(ImportedFile, ImportedFileIndex)
This means that I don't have to bloat my database with all my rendered HTML, but have the full HTML stored in Solr which works for querying.
CommentsIn my work in ReadTheDocs, we now support all of the major VCS repositories: svn, bzr, hg, and git. At this point in time we're only checking out the repos to their default branches, and then trying to trying to update them again to another revision. While writing this code I have had at least 3 different bugs that caused the repos not to be updated correctly. So I'm going to detail here the exact code that allows me to do this for each of these types of repos, hopefully so that when you or I need to do this in the future, we can at least start from here.
Let me know if any of these are wrong, because they probably are.
Checking out a repo:
git clone --depth=1 <remote_url> <local_filepath>
Updating a repo:
git --git-dir=.git fetch
git --git-dir=.git reset --hard origin/master
I'm specifying the --git-dir here because my master repository is git as well, and I don't want to risk the git commands cascading up and applying to the outer repository. I'm also specifying --depth=1 so that I don't clone the entire repository, but only the latest commit. I don't need the history, so I'm doing this. As you can tell, I'm more familiar with git than the other VCS systems here.
Checking out a repo:
svn checkout <remote_url> <local_filepath>
Updating a repo:
svn revert --recursive .
svn up --accept theirs-full
I ran into problems here where I was calling revert without recursive and it wasn't doing anything! You need to do this from the top-level of the repo, and it will make sure all the state lower in the repo is reverted.
Checking out a repo:
bzr checkout <remote_url> <local_filepath>
Updating a repo:
bzr revert
bzr up
This one is nice and easy.
Checking out a repo:
hg clone <remote_url> <local_filepath>
Updating a repo:
hg pull
hg update -C .
Again, a slightly different syntax to make sure that you're deleting all the files, and with the update command.
I hope this helps people in the future at least get to the point where they can pull down code and update it. My next task is figuring out how to support branching in all of the different repositories which is going to be fun, because they have different filesystem structures.
CommentsToday I went ahead and flipped the switch on a couple of server migrations I've had queued up. One of these updates is moving ReadTheDocs over to its own dedicated server, that I built up over the week in my Chef Tutorials.
Over at RTD, you won't notice too many changes, other than it should be FASTER! I had a bunch of sites running on an underpowered server, and now I have it set up nicely, and running on it's own machine, it's chugging along great.
The other change is that I migrated my blog (what you're reading!) over to Mingus. I was running an oold copy of django-basic-blog, which is what Mingus is based off, so the migration was easy. I moved it over from my legacy Slicehost account onto my new server infrastructure that I've been building. There is also a slight refresh of the theme of the sight, mainly the Mingus defaults poking through on top of my old theme.
The other bits you might notice is that my code snippets should now be syntax highlighted. It seems pygments gets confused sometimes, but it's better than it was.
Please report any bugs that you see on either of the sites above to me on Twitter, or here in the comments. Thanks!
CommentsAlternate title: There's no place like home!
This is Part 4, the final part, of my Chef tutorial. Today we're talking about the odds and ends left over to make the server nice to use. You can check out the first 3 parts of the series:
Today's code will be in the git repo under the tag blog-post-4.
So we have our app server up and running, and ready for traffic. Now we just need to add some other bits around the outside for it to be fully functioning and nice to use.
Let's get started.
For doing monitoring with munin, we're going to need to learn our final Chef concept, which is Templates. You should be pretty familiar with them already, except they use Erb, which is a template language that lets you embed Ruby.
We're only going to be configuring the Munin node here. This assumes that you have a munin server running on another machine that you want to give access to monitor your new app server. These configs depend on you putting an entry like this in your node.json, which points at the IP of the master server:
"munin_servers": ["10.177.243.34"],
Then here is how you would write the Recipe.
cookbooks/main/recipes/munin.rb
package "munin-node" do
:upgrade
end
service "munin-node" do
enabled true
running true
supports :status => true, :restart => true, :reload => true
action [:enable, :start]
end
if node.attribute?("munin_servers")
template "/etc/munin/munin-node.conf" do
source "munin-node.conf"
mode 0640
owner "root"
group "root"
variables :munin_servers => node[:munin_servers] || []
notifies :restart, resources(:service => "munin-node")
end
end
The template Resource here is the interesting part. We're surrounding it with a conditional, that makes sure that we're defined a 'munin_servers' key in our node.json. Then we're saying that we're going to render the munin-node.conf file with the source template 'munin-node.conf'. This template will be given the extra varibale 'munin_servers', which is passed in using the variables attribute.
Template are placed inside the cookbook in a similar place to files.
cookbooks/main/templates/default/munin-node.conf
<% @munin_servers.each do |server| -%>
allow ^<%= server.to_s.gsub(/\./, '\.') %>$
<% end -%>
allow ^127\.0\.0\.1$
host *
port 4949
log_level 4
log_file /var/log/munin/munin-node.log
pid_file /var/run/munin/munin-node.pid
background 1
setsid 1
user root
group root
ignore_file ~$
ignore_file DEADJOE$
ignore_file \.bak$
ignore_file %$
ignore_file \.dpkg-(tmp|new|old|dist)$
ignore_file \.rpm(save|new)$
ignore_file \.pod$
The interesting part here is the iteration over the munin_servers list. It's just doing a simple ruby loop, and then outputting the IP address that it contains into the format that munin's configuration file expects.
Note: This data-driven template rendering is a really powerful idiom, and one of my favorite parts about Chef. This allows you to add a new server to your pool, and have all of your configuration files updated automatically across all your server. This is hugely powerful, and one of the primary wins of Configuration Management. This will be shown to better effect in the /etc/hosts file later.
Installing celery is much akin to Gunicorn that was discussed yesterady. The dependencies were installed from our pip requirements file, and we just need to make it run in upstart. We'll be doing that with the following setup.
Additions to cookbooks/main/recipes/readthedocs.rb
cookbook_file "/etc/init/readthedocs-celery.conf" do
source "celery.conf"
owner "root"
group "root"
mode 0644
notifies :restart, resources(:service => "readthedocs-celery")
end
service "readthedocs-celery" do
provider Chef::Provider::Service::Upstart
enabled true
running true
supports :restart => true, :reload => true, :status => true
action [:enable, :start]
end
cookbooks/main/files/celery.conf
description "Celery for ReadTheDocs"
start on runlevel [2345]
stop on runlevel [!2345]
#Send KILL after 20 seconds
kill timeout 20
script
exec sudo -i -u docs django-admin.py celeryd -f /home/docs/sites/readthedocs.org/run/celery.log -c 2 -E -B
end script
respawn
There isn't anything new or interesting here. Just more of the same as before, to get another piece of infrastructure up and running.
I'm a big fan of not enabling services that aren't running as a fundamental security practice, but having a basic firewall to make sure that those are the only ports open isn't a bad idea either. I'm not a great expert, so this is probably the weakest part of my knowledge in this series, so take it with a grain of salt.
My favorite firewall utility is ufw. It makes managing your firewall really simple. Here is my super basic way to configure my firewall, it pretty much sucks :)
cookbooks/main/recipes/security.rb
package "ufw" do
:upgrade
service "ufw" do
enabled true
running true
supports :status => true, :restart => true, :reload => true
action [:enable, :start]
end
bash "Enable UFW" do
user "root"
code <<-EOH
ufw allow 22 #SSH
ufw allow 80 #Nginx
ufw allow 4949 #Munin
EOH
end
As you can see, we're just enabling SSH, Nginx, and Munin. If we need to install any more packages, we'll need to expicitly open a port, which is usually good to remind me that I'm doing it.
Whenever I'm in the cloud, I find keeping track of my other servers to be a pain. You generally want to use the internal backplane to communicate between your servers, so I use the /etc/hosts file to make that simple.
We're going to depend on an entry in your node.json that looks something like this:
"all_servers": {"Golem": ["10.177.234.234", "173.203.234.234"],
"Chimera": ["10.177.234.234", "204.232.234.234"],
"Hydra": ["10.177.234.234", "173.203.234.234"] }
Which is a mapping of all your servers, with their internal and external IPs. This will be useful to have for lots of different recipes, and it would be nice to autogenerate this, but when you only have a few servers it isn't so bad.
The rest of out hosts configuration looks like this:
Addition to cookbooks/main/recipes/default.rb
if node.attribute?("all_servers")
template "/etc/hosts" do
source "hosts"
mode 644
variables :all_servers => node[:all_servers] || {}
end
end
cookbooks/main/templates/default/hosts
127.0.0.1 localhost localhost.localdomain
<% @all_servers.each_pair do |name, ips| -%>
<%= ips[0] %> <%= ips[1] %> <%= name %>
<% end -%>
As you can see, when we add a server to the all_servers hash, it will propogate out to the /etc/hosts file of our app server. This makes me really happy, and showcases some of the more advanced use cases of Chef.
Now that we have the server all set up, it won't be much good if it isn't nice to use when we shell in. So here is how I go ahead and add in some nicities to bash for when you log in.
Addition to cookbooks/main/recipes/readthedocs.rb
cookbook_file "/home/docs/.bash_profile" do
source "bash_profile"
owner "docs"
group "docs"
mode 0755
end
cookbooks/main/files/default/bash_profile
. .bashrc
export PIP_DOWNLOAD_CACHE=/tmp/pip
export DJANGO_SETTINGS_MODULE=settings
export PYTHONPATH=$PYTHONPATH:~/sites/readthedocs.org/checkouts/readthedocs.org
export EDITOR=vim
. sites/readthedocs.org/bin/activate
cd ~/sites/readthedocs.org/
alias chk='cd /home/docs/sites/readthedocs.org/checkouts/readthedocs.org'
alias run='cd /home/docs/sites/readthedocs.org/run'
First off, we're sourcing the .bashrc file, so that we get all the nice things it provides, like a colored PS1. Then we're setting some environment variables so that django-admin.py and pip work nicely. Then we activate our virtualenv and switch into it's base directory, so we're always starting where we want to be on login. Then we just have a couple of aliases for easy navigation around.
I like how this makes the user experience of shelling into the server a lot nicer, and makes the manual workflow that you'll eventually have to fiddle with really nice.
So that's the end of this tutorial. I hope that it was instructive in learning Chef, as well as providing some insights into the deployment of a Django application. Tomorrow (or if I'm too tired, next week), I'll be providing some thoughts on how I think chef treated me, and how I feel about the build out.
CommentsWelcome to the home of Eric Holscher on the web. I talk about software development, mostly in the realm of Django. I am interested in the real time web, testing, mobile apps, and other things.
Why Read the Docs matters
5 days, 10 hours ago (Comments: 7)
Read the Docs Update
9 months, 3 weeks ago (Comments: 2)
Using Reviewboard with Git
1 year ago (Comments: 0)
Read the Docs Updates
1 year ago (Comments: 1)
Handling Django Settings Files
1 year ago (Comments: 12)
Required Reading
1 year, 2 months ago (Comments: 0)
Using Haystack to index non-database content
1 year, 2 months ago (Comments: 4)
Correct commands to check out and update VCS repos
1 year, 2 months ago (Comments: 0)
Site upgrades
1 year, 2 months ago (Comments: 0)
Building a Django App Server with Chef: Part 4
1 year, 2 months ago (Comments: 1)
Setting up Django and mod_wsgi
Building a Django App Server with Chef: Part 1
Screencast: Django Command Extensions
Big list of Django tips (and some python tips too)
Handling Django Settings Files
Lessons Learned From The Dash: Easy Django Deployment
Large Problems in Django, Mostly Solved: Delayed Execution
Building a Django App Server with Chef: Part 2

