Showing posts with label django. Show all posts
Showing posts with label django. Show all posts

Tuesday, July 23, 2013

Introducing virtualenvwrapper-django

I recently took one of my previous posts entitled "Never Have to Type "python manage.py" with Django Again", and combined it with another bash function to auto set DJANGO_MODULE_SETTINGS by interrogating a Django project's directory. I then rolled everything together into github as


Its a little rough I admit, and I welcome any feedback for improvements.  As a result, I'm now able to run commands like the following anywhere once I have my virtual environment loaded.

(spock)jbisbee@tacquito:~/src/spock$ manage dbshell
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 95
Server version: 5.5.31-0ubuntu0.12.04.2 (Ubuntu)

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

Edit: I've gotten some feedback and I realize I didn't sell the real reason behind dynamically setting the DJANGO_SETTINGS_MODULE variable. I use Django at work and we have moved from the generic one file settings.py to a settings directory which looks something like this

  settings/
    base.py
    dev.py           # imports from base.py
    test.py          # imports from base.py
    production.py    # imports from base.py
    jbisbee.py       # imports from dev.py

This way you can have cascading Django settings with overrides at multiple levels. This bash function I wrote finds the settings directory and will set DJANGO_SETTINGS_MODULE to the user's specific dev settings (or dev if the user can't be found)

DJANGO_SETTINGS_MODULE=projectname.settings.jbisbee

This way in jbisbee.py I can override my database, turn on crazy debugging, or do anything else I want and be able to commit those settings without affecting others.

Disclaimer: If you manage your Django project's settings differently let me know. I'd love to make the determine_django_module_settings more flexible and patches are more than welcome!

Saturday, July 13, 2013

Django's DateTimeField auto_now* Option Caveats

I'm new to Python and Django and had a run in with DateTimeField's auto_now and auto_now_new. I'm importing data from an existing database and there currently is a column called upd_stamp which I've renamed to modified. Looking through the documentation, I was happy that I could just add auto_now to the options to get the behavior I wanted without any additional code. Well it didn't and I'll explain why as well as the solution I came up with (thanks to a bit of Goggling and Stack Overflow).

created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)

The problem I ran into was that I wanted to keep the legacy timestamp from the old database when I loaded the record for the first time then have the auto_now kick in. The problem is that auto_now is very stupid (as it should be) and doesn't understand the concept of insert or update and whether your new record is coming in with pre-populated DateTimeField attribute. It gladly throws away any value away and always replaces the value with the current time. I didn't notice it on first import but soon realized auto_now wouldn't meet my needs.

After a quick search, I found this Stack Overflow question that had the perfect fix for my problem. It suggested implementing my own save that would allow me to pass in values for created or modified but would fall back to the current time. I changed the code a bit because I didn't need a created timestamp for my model, but this should give you an idea.

Note: you have to remove any additional values from kwargs before passing to the parent class or super.save will complain that created and modified aren't valid arguments.

import datetime

class User(models.Model):
    created  = models.DateTimeField(editable=False)
    modified = models.DateTimeField()

   def save(self, *args, **kwargs):

        # original Code: called datetime.datetime.today()
        #                multiple places.  Modified kwargs
        #
        # created = kwargs.pop('created', None)
        # if created:
        #     self.created = created
        # elif not self.id:
        #     self.created = datetime.datetime.today()
        # 
        # modified = kwargs.pop('modified', None)
        # if modified:
        #     self.modified = modified
        # else:
        #     self.modified = datetime.datetime.today()

        # Edit #1: Changed example to more efficient 
        #          version from blog comments.
        #          single today()
        # 
        # today = datetime.datetime.today()
        #
        # if not self.id
        #     self.created = kwargs.pop('created', today)
        # elif kwargs.get('created', None)
        #     self.created = kwargs.pop('created')
        #
        # self.modified = kwargs.pop('modified', today)


        # Edit #2: no longer pass arguments via save
        #          was orignially how I wanted to solve
        #          the problem and how I felt auto_now
        #          should have worked.  Thanks to
        #          indosauros from reddit. :)
        #
        #          On inital creation of the object
        #          only set today if values are not
        #          already populated.  auto_now*
        #          wipes them out regardless

        today = datetime.datetime.today()

        if not self.id:
            if not self.created:
                self.created = today
            if not self.modified:
                self.modified = today
        else:
            self.modified = today

        super(User, self).save(*args, **kwargs)

If you see any glaring python or style related issues, please let me know. I'm still finding my way pythonwise and would love any best practices feedback.

Wednesday, July 3, 2013

Never Have to Type "python manage.py" with Django Again

I got very quickly got tired making sure I was in the right directory and typing python manage.py. I found django-shortcuts but didn't like the fact that it only mapped a handful of the commands and I wanted access to everything manage.py could do.

I then briefly considered creating a bash function that would determine manage.py's location on every invocation but figured that was silly and decided I might as leverage virtualenv's virtual environment and as Ron Popiel says, "You set it and forget it!"

It only took a minute of poking to find the postactivate hook file within my ~/.virtualenvs directory. Below is the fruit of my labor.

#!/bin/bash
# ~/.virtualenvs/postactive
# This hook is run after every virtualenv is activated.
VIRTUAL_ENV_NAME=$(basename $VIRTUAL_ENV)
SRC_DIR="$HOME/src"
if [ -d "$SRC_DIR/$VIRTUAL_ENV_NAME" ]
then
    MANAGE_PY=$(find "$SRC_DIR/$VIRTUAL_ENV_NAME" -name "manage.py" -type f)
    if [ -e "$MANAGE_PY" ]
    then
        alias django="python $MANAGE_PY"
    else
        unalias django
    fi
else
    unalias django
fi

The end result is that I have a new django alias that will work anywhere and act as though I typed python manage.py

jbisbee@beni:~$ workon django-tutorial
(django-tutorial)jbisbee@beni:~$ alias | grep django
alias django='python /Users/jbisbee/src/django-tutorial/mysite/manage.py'
(django-tutorial)jbisbee@beni:~$

Disclaimer: I'm making a big assumption here that the virtualenv name you're using is identical to the name of the project you're working on. It's a pretty big assumption so I apologize if it does't work right out of the box for you.