Commits

Colin Copeland committed ce62ed5 Merge

merge default into stable

Comments (0)

Files changed (35)

 syntax: glob
 *.pyc
 *.egg*
+local_settings.py
+*.db

example_project/__init__.py

Empty file added.

example_project/manage.py

+#!/usr/bin/env python
+from django.core.management import execute_manager
+import imp
+try:
+    imp.find_module('settings') # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
+    sys.exit(1)
+
+import settings
+
+if __name__ == "__main__":
+    execute_manager(settings)

example_project/requirements.txt

+django==1.3
+python-dateutil==1.5
+django-ajax-selects==1.1.4
+django-pagination==1.0.7
+textile==2.1.4

example_project/settings.py

+# Django settings for example_project project.
+
+from os import path
+
+PROJECT_PATH = path.abspath('%s' % path.dirname(__file__))
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': 'timepiece.db',                      # Or path to database file if using sqlite3.
+        'USER': '',                      # Not used with sqlite3.
+        'PASSWORD': '',                  # Not used with sqlite3.
+        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
+        '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.
+# On Unix systems, a value of None will cause Django to use the same
+# timezone as the operating system.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/New_York'
+
+# 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
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale
+USE_L10N = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/home/media/media.lawrence.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/home/media/media.lawrence.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://media.lawrence.com/static/"
+STATIC_URL = '/static/'
+
+# URL prefix for admin static files -- CSS, JavaScript and images.
+# Make sure to use a trailing slash.
+# Examples: "http://foo.com/static/admin/", "/static/admin/".
+ADMIN_MEDIA_PREFIX = '/static/admin/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+    '%s/static' % PROJECT_PATH,
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+    'django.contrib.staticfiles.finders.FileSystemFinder',
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+#    'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '4l*4*t$1gr=r7wwul*=i*0^&_cd4!jz0)6pn$@46ua@omtxxwo'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader',
+    'django.template.loaders.app_directories.Loader',
+#     'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'pagination.middleware.PaginationMiddleware',
+)
+
+ROOT_URLCONF = 'example_project.urls'
+
+TEMPLATE_DIRS = (
+    '%s/templates' % PROJECT_PATH,
+)
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+    "django.contrib.auth.context_processors.auth",
+    "django.core.context_processors.debug",
+    "django.core.context_processors.i18n",
+    "django.core.context_processors.media",
+    "django.core.context_processors.static",
+    "django.contrib.messages.context_processors.messages",
+    'django.core.context_processors.request',
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'django.contrib.admin',
+    'django.contrib.markup',
+    'pagination',
+    'ajax_select',
+    'timepiece',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'handlers': {
+        'mail_admins': {
+            'level': 'ERROR',
+            'class': 'django.utils.log.AdminEmailHandler'
+        }
+    },
+    'loggers': {
+        'django.request': {
+            'handlers': ['mail_admins'],
+            'level': 'ERROR',
+            'propagate': True,
+        },
+    }
+}
+
+AJAX_LOOKUP_CHANNELS = {
+    'user': ('timepiece.lookups', 'UserLookup'),
+    'quick_search' : ('timepiece.lookups', 'QuickLookup'),
+}

example_project/static/css/jquery.autocomplete.css

+.ac_results {
+	padding: 0px;
+	border: 1px solid black;
+	background-color: white;
+	overflow: hidden;
+	z-index: 99999;
+}
+
+.ac_results ul {
+	width: 100%;
+	list-style-position: outside;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+.ac_results li {
+	margin: 0px;
+	padding: 2px 5px;
+	cursor: default;
+	display: block;
+	/* 
+	if width will be 100% horizontal scrollbar will apear 
+	when scroll mode will be used
+	*/
+	/*width: 100%;*/
+	font: menu;
+	font-size: 12px;
+	/* 
+	it is very important, if line-height not setted or setted 
+	in relative units scroll will be broken in firefox
+	*/
+	line-height: 16px;
+	overflow: hidden;
+}
+
+.ac_loading {
+	background: white url('indicator.gif') right center no-repeat;
+}
+
+.ac_odd {
+	background-color: #eee;
+}
+
+.ac_over {
+	background-color: #0A246A;
+	color: white;
+}

example_project/static/js/jquery.autocomplete.min.js

+/*
+ * jQuery Autocomplete plugin 1.1
+ *
+ * Copyright (c) 2009 Jörn Zaefferer
+ *
+ * Dual licensed under the MIT and GPL licenses:
+ *   http://www.opensource.org/licenses/mit-license.php
+ *   http://www.gnu.org/licenses/gpl.html
+ *
+ * Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
+ */;(function($){$.fn.extend({autocomplete:function(urlOrData,options){var isUrl=typeof urlOrData=="string";options=$.extend({},$.Autocompleter.defaults,{url:isUrl?urlOrData:null,data:isUrl?null:urlOrData,delay:isUrl?$.Autocompleter.defaults.delay:10,max:options&&!options.scroll?10:150},options);options.highlight=options.highlight||function(value){return value;};options.formatMatch=options.formatMatch||options.formatItem;return this.each(function(){new $.Autocompleter(this,options);});},result:function(handler){return this.bind("result",handler);},search:function(handler){return this.trigger("search",[handler]);},flushCache:function(){return this.trigger("flushCache");},setOptions:function(options){return this.trigger("setOptions",[options]);},unautocomplete:function(){return this.trigger("unautocomplete");}});$.Autocompleter=function(input,options){var KEY={UP:38,DOWN:40,DEL:46,TAB:9,RETURN:13,ESC:27,COMMA:188,PAGEUP:33,PAGEDOWN:34,BACKSPACE:8};var $input=$(input).attr("autocomplete","off").addClass(options.inputClass);var timeout;var previousValue="";var cache=$.Autocompleter.Cache(options);var hasFocus=0;var lastKeyPressCode;var config={mouseDownOnSelect:false};var select=$.Autocompleter.Select(options,input,selectCurrent,config);var blockSubmit;$.browser.opera&&$(input.form).bind("submit.autocomplete",function(){if(blockSubmit){blockSubmit=false;return false;}});$input.bind(($.browser.opera?"keypress":"keydown")+".autocomplete",function(event){hasFocus=1;lastKeyPressCode=event.keyCode;switch(event.keyCode){case KEY.UP:event.preventDefault();if(select.visible()){select.prev();}else{onChange(0,true);}break;case KEY.DOWN:event.preventDefault();if(select.visible()){select.next();}else{onChange(0,true);}break;case KEY.PAGEUP:event.preventDefault();if(select.visible()){select.pageUp();}else{onChange(0,true);}break;case KEY.PAGEDOWN:event.preventDefault();if(select.visible()){select.pageDown();}else{onChange(0,true);}break;case options.multiple&&$.trim(options.multipleSeparator)==","&&KEY.COMMA:case KEY.TAB:case KEY.RETURN:if(selectCurrent()){event.preventDefault();blockSubmit=true;return false;}break;case KEY.ESC:select.hide();break;default:clearTimeout(timeout);timeout=setTimeout(onChange,options.delay);break;}}).focus(function(){hasFocus++;}).blur(function(){hasFocus=0;if(!config.mouseDownOnSelect){hideResults();}}).click(function(){if(hasFocus++>1&&!select.visible()){onChange(0,true);}}).bind("search",function(){var fn=(arguments.length>1)?arguments[1]:null;function findValueCallback(q,data){var result;if(data&&data.length){for(var i=0;i<data.length;i++){if(data[i].result.toLowerCase()==q.toLowerCase()){result=data[i];break;}}}if(typeof fn=="function")fn(result);else $input.trigger("result",result&&[result.data,result.value]);}$.each(trimWords($input.val()),function(i,value){request(value,findValueCallback,findValueCallback);});}).bind("flushCache",function(){cache.flush();}).bind("setOptions",function(){$.extend(options,arguments[1]);if("data"in arguments[1])cache.populate();}).bind("unautocomplete",function(){select.unbind();$input.unbind();$(input.form).unbind(".autocomplete");});function selectCurrent(){var selected=select.selected();if(!selected)return false;var v=selected.result;previousValue=v;if(options.multiple){var words=trimWords($input.val());if(words.length>1){var seperator=options.multipleSeparator.length;var cursorAt=$(input).selection().start;var wordAt,progress=0;$.each(words,function(i,word){progress+=word.length;if(cursorAt<=progress){wordAt=i;return false;}progress+=seperator;});words[wordAt]=v;v=words.join(options.multipleSeparator);}v+=options.multipleSeparator;}$input.val(v);hideResultsNow();$input.trigger("result",[selected.data,selected.value]);return true;}function onChange(crap,skipPrevCheck){if(lastKeyPressCode==KEY.DEL){select.hide();return;}var currentValue=$input.val();if(!skipPrevCheck&&currentValue==previousValue)return;previousValue=currentValue;currentValue=lastWord(currentValue);if(currentValue.length>=options.minChars){$input.addClass(options.loadingClass);if(!options.matchCase)currentValue=currentValue.toLowerCase();request(currentValue,receiveData,hideResultsNow);}else{stopLoading();select.hide();}};function trimWords(value){if(!value)return[""];if(!options.multiple)return[$.trim(value)];return $.map(value.split(options.multipleSeparator),function(word){return $.trim(value).length?$.trim(word):null;});}function lastWord(value){if(!options.multiple)return value;var words=trimWords(value);if(words.length==1)return words[0];var cursorAt=$(input).selection().start;if(cursorAt==value.length){words=trimWords(value)}else{words=trimWords(value.replace(value.substring(cursorAt),""));}return words[words.length-1];}function autoFill(q,sValue){if(options.autoFill&&(lastWord($input.val()).toLowerCase()==q.toLowerCase())&&lastKeyPressCode!=KEY.BACKSPACE){$input.val($input.val()+sValue.substring(lastWord(previousValue).length));$(input).selection(previousValue.length,previousValue.length+sValue.length);}};function hideResults(){clearTimeout(timeout);timeout=setTimeout(hideResultsNow,200);};function hideResultsNow(){var wasVisible=select.visible();select.hide();clearTimeout(timeout);stopLoading();if(options.mustMatch){$input.search(function(result){if(!result){if(options.multiple){var words=trimWords($input.val()).slice(0,-1);$input.val(words.join(options.multipleSeparator)+(words.length?options.multipleSeparator:""));}else{$input.val("");$input.trigger("result",null);}}});}};function receiveData(q,data){if(data&&data.length&&hasFocus){stopLoading();select.display(data,q);autoFill(q,data[0].value);select.show();}else{hideResultsNow();}};function request(term,success,failure){if(!options.matchCase)term=term.toLowerCase();var data=cache.load(term);if(data&&data.length){success(term,data);}else if((typeof options.url=="string")&&(options.url.length>0)){var extraParams={timestamp:+new Date()};$.each(options.extraParams,function(key,param){extraParams[key]=typeof param=="function"?param():param;});$.ajax({mode:"abort",port:"autocomplete"+input.name,dataType:options.dataType,url:options.url,data:$.extend({q:lastWord(term),limit:options.max},extraParams),success:function(data){var parsed=options.parse&&options.parse(data)||parse(data);cache.add(term,parsed);success(term,parsed);}});}else{select.emptyList();failure(term);}};function parse(data){var parsed=[];var rows=data.split("\n");for(var i=0;i<rows.length;i++){var row=$.trim(rows[i]);if(row){row=row.split("|");parsed[parsed.length]={data:row,value:row[0],result:options.formatResult&&options.formatResult(row,row[0])||row[0]};}}return parsed;};function stopLoading(){$input.removeClass(options.loadingClass);};};$.Autocompleter.defaults={inputClass:"ac_input",resultsClass:"ac_results",loadingClass:"ac_loading",minChars:1,delay:400,matchCase:false,matchSubset:true,matchContains:false,cacheLength:10,max:100,mustMatch:false,extraParams:{},selectFirst:true,formatItem:function(row){return row[0];},formatMatch:null,autoFill:false,width:0,multiple:false,multipleSeparator:", ",highlight:function(value,term){return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)("+term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi,"\\$1")+")(?![^<>]*>)(?![^&;]+;)","gi"),"<strong>$1</strong>");},scroll:true,scrollHeight:180};$.Autocompleter.Cache=function(options){var data={};var length=0;function matchSubset(s,sub){if(!options.matchCase)s=s.toLowerCase();var i=s.indexOf(sub);if(options.matchContains=="word"){i=s.toLowerCase().search("\\b"+sub.toLowerCase());}if(i==-1)return false;return i==0||options.matchContains;};function add(q,value){if(length>options.cacheLength){flush();}if(!data[q]){length++;}data[q]=value;}function populate(){if(!options.data)return false;var stMatchSets={},nullData=0;if(!options.url)options.cacheLength=1;stMatchSets[""]=[];for(var i=0,ol=options.data.length;i<ol;i++){var rawValue=options.data[i];rawValue=(typeof rawValue=="string")?[rawValue]:rawValue;var value=options.formatMatch(rawValue,i+1,options.data.length);if(value===false)continue;var firstChar=value.charAt(0).toLowerCase();if(!stMatchSets[firstChar])stMatchSets[firstChar]=[];var row={value:value,data:rawValue,result:options.formatResult&&options.formatResult(rawValue)||value};stMatchSets[firstChar].push(row);if(nullData++<options.max){stMatchSets[""].push(row);}};$.each(stMatchSets,function(i,value){options.cacheLength++;add(i,value);});}setTimeout(populate,25);function flush(){data={};length=0;}return{flush:flush,add:add,populate:populate,load:function(q){if(!options.cacheLength||!length)return null;if(!options.url&&options.matchContains){var csub=[];for(var k in data){if(k.length>0){var c=data[k];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub.push(x);}});}}return csub;}else
+if(data[q]){return data[q];}else
+if(options.matchSubset){for(var i=q.length-1;i>=options.minChars;i--){var c=data[q.substr(0,i)];if(c){var csub=[];$.each(c,function(i,x){if(matchSubset(x.value,q)){csub[csub.length]=x;}});return csub;}}}return null;}};};$.Autocompleter.Select=function(options,input,select,config){var CLASSES={ACTIVE:"ac_over"};var listItems,active=-1,data,term="",needsInit=true,element,list;function init(){if(!needsInit)return;element=$("<div/>").hide().addClass(options.resultsClass).css("position","absolute").appendTo(document.body);list=$("<ul/>").appendTo(element).mouseover(function(event){if(target(event).nodeName&&target(event).nodeName.toUpperCase()=='LI'){active=$("li",list).removeClass(CLASSES.ACTIVE).index(target(event));$(target(event)).addClass(CLASSES.ACTIVE);}}).click(function(event){$(target(event)).addClass(CLASSES.ACTIVE);select();input.focus();return false;}).mousedown(function(){config.mouseDownOnSelect=true;}).mouseup(function(){config.mouseDownOnSelect=false;});if(options.width>0)element.css("width",options.width);needsInit=false;}function target(event){var element=event.target;while(element&&element.tagName!="LI")element=element.parentNode;if(!element)return[];return element;}function moveSelect(step){listItems.slice(active,active+1).removeClass(CLASSES.ACTIVE);movePosition(step);var activeItem=listItems.slice(active,active+1).addClass(CLASSES.ACTIVE);if(options.scroll){var offset=0;listItems.slice(0,active).each(function(){offset+=this.offsetHeight;});if((offset+activeItem[0].offsetHeight-list.scrollTop())>list[0].clientHeight){list.scrollTop(offset+activeItem[0].offsetHeight-list.innerHeight());}else if(offset<list.scrollTop()){list.scrollTop(offset);}}};function movePosition(step){active+=step;if(active<0){active=listItems.size()-1;}else if(active>=listItems.size()){active=0;}}function limitNumberOfItems(available){return options.max&&options.max<available?options.max:available;}function fillList(){list.empty();var max=limitNumberOfItems(data.length);for(var i=0;i<max;i++){if(!data[i])continue;var formatted=options.formatItem(data[i].data,i+1,max,data[i].value,term);if(formatted===false)continue;var li=$("<li/>").html(options.highlight(formatted,term)).addClass(i%2==0?"ac_even":"ac_odd").appendTo(list)[0];$.data(li,"ac_data",data[i]);}listItems=list.find("li");if(options.selectFirst){listItems.slice(0,1).addClass(CLASSES.ACTIVE);active=0;}if($.fn.bgiframe)list.bgiframe();}return{display:function(d,q){init();data=d;term=q;fillList();},next:function(){moveSelect(1);},prev:function(){moveSelect(-1);},pageUp:function(){if(active!=0&&active-8<0){moveSelect(-active);}else{moveSelect(-8);}},pageDown:function(){if(active!=listItems.size()-1&&active+8>listItems.size()){moveSelect(listItems.size()-1-active);}else{moveSelect(8);}},hide:function(){element&&element.hide();listItems&&listItems.removeClass(CLASSES.ACTIVE);active=-1;},visible:function(){return element&&element.is(":visible");},current:function(){return this.visible()&&(listItems.filter("."+CLASSES.ACTIVE)[0]||options.selectFirst&&listItems[0]);},show:function(){var offset=$(input).offset();element.css({width:typeof options.width=="string"||options.width>0?options.width:$(input).width(),top:offset.top+input.offsetHeight,left:offset.left}).show();if(options.scroll){list.scrollTop(0);list.css({maxHeight:options.scrollHeight,overflow:'auto'});if($.browser.msie&&typeof document.body.style.maxHeight==="undefined"){var listHeight=0;listItems.each(function(){listHeight+=this.offsetHeight;});var scrollbarsVisible=listHeight>options.scrollHeight;list.css('height',scrollbarsVisible?options.scrollHeight:listHeight);if(!scrollbarsVisible){listItems.width(list.width()-parseInt(listItems.css("padding-left"))-parseInt(listItems.css("padding-right")));}}}},selected:function(){var selected=listItems&&listItems.filter("."+CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);return selected&&selected.length&&$.data(selected[0],"ac_data");},emptyList:function(){list&&list.empty();},unbind:function(){element&&element.remove();}};};$.fn.selection=function(start,end){if(start!==undefined){return this.each(function(){if(this.createTextRange){var selRange=this.createTextRange();if(end===undefined||start==end){selRange.move("character",start);selRange.select();}else{selRange.collapse(true);selRange.moveStart("character",start);selRange.moveEnd("character",end);selRange.select();}}else if(this.setSelectionRange){this.setSelectionRange(start,end);}else if(this.selectionStart){this.selectionStart=start;this.selectionEnd=end;}});}var field=this[0];if(field.createTextRange){var range=document.selection.createRange(),orig=field.value,teststring="<->",textLength=range.text.length;range.text=teststring;var caretAt=field.value.indexOf(teststring);field.value=orig;this.selection(caretAt,caretAt+textLength);return{start:caretAt,end:caretAt+textLength}}else if(field.selectionStart!==undefined){return{start:field.selectionStart,end:field.selectionEnd}}};})(jQuery);

example_project/templates/base.html

+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+	  <meta http-equiv='content-type' content='text/html; charset=iso-8859-1' />
+	  <title>{% block title %}django-timepiece{% endblock %}</title>
+	  <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.9.0/build/fonts/fonts-min.css">
+    <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.9.0/build/base/base-min.css">
+	  <link charset='UTF-8' rel="stylesheet" type="text/css" media="screen" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" />
+	  <link charset="UTF-8" rel="stylesheet" type="text/css" media="screen" href="{{ STATIC_URL }}css/jquery.autocomplete.css" />
+	  <link charset='UTF-8' rel="stylesheet" type="text/css" media="screen" href="{{ STATIC_URL }}css/books.css" />
+	  <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js" type="text/javascript">jQuery.noConflict();</script>
+	  <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js" type="text/javascript"></script>
+	  <script src="{{ STATIC_URL }}js/jquery.autocomplete.min.js" type="text/javascript"></script>
+    {% block javascript %}{% endblock %}
+    {% block css %}{% endblock %}
+</head>
+<body>
+
+<div id="content-wrapper">
+	<div id="content">
+        {% if messages %}
+        <ul class="message_list">
+            {% for message in messages %}
+            <li>{{ message }}</li>
+            {% endfor %}
+        </ul>
+        {% endif %}
+        {% if notifications %}
+            <ul class="message_list">
+                {% for message in notifications %}
+                <li{% if message.tags %} 
+                        class="{{ message.tags }}"
+                    {% endif %}>
+                    {{ message }}
+                </li>
+                {% endfor %}
+            </ul>
+        {% endif %}
+        <h1>django-timepiece</h1>
+        <ul>
+            <li><a href='{% url timepiece-entries %}'>Time Tracking</a></li>
+            <li><a href='{% url list_people %}'>People</a></li>
+            <li><a href='{% url list_businesses %}'>Businesses</a></li>
+            <li><a href='{% url list_projects %}'>Projects</a></li>
+        </ul>
+		{% block content %}{% endblock %}
+		<div class="cleaner"></div>
+	</div>
+</div>
+
+</body>
+</html>

example_project/urls.py

+from django.conf.urls.defaults import patterns, include, url
+
+# Uncomment the next two lines to enable the admin:
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+    url(r'^admin/', include(admin.site.urls)),
+    url(r'^ajax/', include('ajax_select.urls')),
+    url(r'', include('timepiece.urls')),
+)

timepiece/__init__.py

 VERSION = (0, 0, 1, '')
 
+
 def version():
     return '%s.%s.%s-%s' % VERSION
 
+
 def get_version():
     return 'django-timepiece %s' % version()

timepiece/admin.py

 
 from timepiece.projection import run_projection
 
+
 class ActivityAdmin(admin.ModelAdmin):
     model = timepiece.Activity
     list_display = ('code', 'name', 'billable')
     search_fields = ['user', 'project', 'activity', 'comments']
     date_hierarchy = 'start_time'
     ordering = ('-start_time',)
-    
+
     def project_type(self, entry):
         return entry.project.type
 admin.site.register(timepiece.Entry, EntryAdmin)
     search_fields = ('label', 'type')
     list_display = ('label', 'type', 'enable_timetracking', 'billable')
     list_filter = ('type', 'enable_timetracking', 'billable')
-    ordering = ('type', 'sort_order',) # Django honors only first field
+    #Django honors only first field
+    ordering = ('type', 'sort_order')
 admin.site.register(timepiece.Attribute, AttributeAdmin)
 
 
 
 class ProjectContractAdmin(admin.ModelAdmin):
     model = timepiece.ProjectContract
-    list_display = ('project', 'start_date', 'end_date', 'status', 
+    list_display = ('project', 'start_date', 'end_date', 'status',
                     'num_hours', 'hours_assigned', 'hours_unassigned',
                     'hours_worked')
     ordering = ('-end_date',)
     inlines = (ContractAssignmentInline,)
     list_filter = ('status',)
-    
+
     def hours_unassigned(self, obj):
         return obj.num_hours - obj.hours_assigned
 
 
     def total_available(self, obj):
         return "%.2f" % (obj.hours_available,)
-  
+
     def scheduled(self, obj):
         return "%.2f" % (obj.hours_scheduled,)
-  
+
     def unscheduled(self, obj):
         return "%.2f" % (obj.hours_available - float(obj.hours_scheduled),)
 
 
 
 class AllocationAdmin(admin.ModelAdmin):
-    list_display = ('date','hours', 'hours_worked', 'hours_left',)
+    list_display = ('date', 'hours', 'hours_worked', 'hours_left',)
 admin.site.register(timepiece.AssignmentAllocation, AllocationAdmin)

timepiece/context_processors.py

 
 from timepiece.forms import QuickSearchForm
 
+
 def timepiece_settings(request):
     default_famfamfam_url = settings.STATIC_URL + 'images/icons/'
     famfamfam_url = getattr(settings, 'FAMFAMFAM_URL', default_famfamfam_url)
         'FAMFAMFAM_URL': famfamfam_url,
     }
     return context
-    
+
+
 def quick_search(request):
     return {
         'quick_search_form': QuickSearchForm(),

timepiece/fields.py

 from timepiece.widgets import PendulumDateTimeWidget
 from timepiece.utils import DEFAULT_TIME_FORMATS
 
+
 class PendulumDateTimeField(forms.SplitDateTimeField):
     """
     This custom field is just a way to offer some more friendly ways to enter
     a time, such as 1pm or 8:15 pm
     """
     widget = PendulumDateTimeWidget
-    def __init__(self, date_formats=None, time_formats=None, help_text=None, *args, **kwargs):
+
+    def __init__(self, date_formats=None, time_formats=None, help_text=None,
+        *args, **kwargs):
         time_formats = time_formats or DEFAULT_TIME_FORMATS
         fields = (
             forms.fields.DateField(input_formats=date_formats),
             forms.fields.TimeField(input_formats=time_formats))
-        forms.MultiValueField.__init__(self, fields, help_text=help_text, *args, **kwargs)
+        forms.MultiValueField.__init__(self, fields, help_text=help_text,
+            *args, **kwargs)

timepiece/forms.py

 from ajax_select.fields import AutoCompleteSelectMultipleField, \
                                AutoCompleteSelectField, \
                                AutoCompleteSelectWidget
-                               
+
+
 class CreatePersonForm(auth_forms.UserCreationForm):
     class Meta:
         model = auth_models.User
         fields = (
-            "username", "first_name", "last_name", 
+            "username", "first_name", "last_name",
             "email", "is_active", "is_staff")
 
 
 class EditPersonForm(auth_forms.UserChangeForm):
-    password_one = forms.CharField(required=False, max_length=36, label=_(u'Password'),
-                                widget=forms.PasswordInput(render_value=False))
-    password_two = forms.CharField(required=False, max_length=36, label=_(u'Repeat Password'),
-                                widget=forms.PasswordInput(render_value=False))
+    password_one = forms.CharField(required=False, max_length=36,
+        label=_(u'Password'), widget=forms.PasswordInput(render_value=False))
+    password_two = forms.CharField(required=False, max_length=36,
+        label=_(u'Repeat Password'),
+        widget=forms.PasswordInput(render_value=False))
+
     class Meta:
         model = auth_models.User
         fields = (
-            "username", "first_name", "last_name", 
+            "username", "first_name", "last_name",
             "email", "is_active", "is_staff"
         )
-                        
+
     def clean(self):
         super(EditPersonForm, self).clean()
         password_one = self.cleaned_data.get('password_one', None)
         if password_one and password_one != password_two:
             raise forms.ValidationError(_('Passwords Must Match.'))
         return self.cleaned_data
-    
+
     def save(self, *args, **kwargs):
         commit = kwargs.get('commit', True)
         kwargs['commit'] = False
         if commit:
             instance.save()
         return instance
-        
+
+
 class CharAutoCompleteSelectWidget(AutoCompleteSelectWidget):
     def value_from_datadict(self, data, files, name):
         return data.get(name, None)
         'quick_search',
         widget=CharAutoCompleteSelectWidget('quick_search'),
     )
-    
+
     def clean_quick_search(self):
         item = self.cleaned_data['quick_search']
         if isinstance(item, timepiece.Project):
                 'person_id': item.id,
             })
         raise forms.ValidationError('Must be a User or Project')
-    
+
     def save(self):
         return self.cleaned_data['quick_search']
 
 
 class AddUserToProjectForm(forms.Form):
     user = AutoCompleteSelectField('user')
-    
+
     def save(self):
         return self.cleaned_data['user']
 
             settings,
             'TIMEPIECE_DEFAULT_LOCATION_SLUG',
             None,
-        )        
+        )
         if default_loc:
             try:
                 loc = timepiece.Location.objects.get(slug=default_loc)
             except timepiece.Location.DoesNotExist:
                 loc = None
-            if loc:                
+            if loc:
                 initial = kwargs.get('initial', {})
                 initial['location'] = loc.pk
-                
         super(ClockInForm, self).__init__(*args, **kwargs)
         self.fields['start_time'].required = False
         self.fields['start_time'].initial = datetime.now()
             Q(status__enable_timetracking=True) &
             Q(type__enable_timetracking=True)
         )
-        
+
         try:
             profile = self.user.profile
         except timepiece.UserProfile.DoesNotExist:
             pass
         else:
             if profile.default_activity:
-                self.fields['activity'].initial = profile.default_activity    
+                self.fields['activity'].initial = profile.default_activity
+        #model validation requires Entry.user to be set, so let's set it now
+        self.instance.user = self.user
 
-            
+    def clean_start_time(self):
+        """
+        Make sure that the start time doesn't come before the active entry
+        """
+        start = self.cleaned_data['start_time']
+        active_entries = self.user.timepiece_entries.filter(
+            start_time__gte=start, end_time__isnull=True)
+        for entry in active_entries:
+            output = 'The start time is on or before the current entry: ' + \
+            '%s - %s starting at %s' % (entry.project, entry.activity,
+                entry.start_time.strftime('%H:%M:%S'))
+            raise forms.ValidationError(output)
+        return start
+
     def save(self, commit=True):
         entry = super(ClockInForm, self).save(commit=False)
         entry.hours = 0
     class Meta:
         model = timepiece.Entry
         fields = ('location', 'comments', 'start_time', 'end_time')
-        
+
     def __init__(self, *args, **kwargs):
-        kwargs['initial'] = {'end_time': datetime.now()}  
+        kwargs['initial'] = {'end_time': datetime.now()}
         super(ClockOutForm, self).__init__(*args, **kwargs)
         self.fields['start_time'] = forms.DateTimeField(
             widget=forms.SplitDateTimeWidget(
             ),
         )
 
-        self.fields.keyOrder = ('location', 'start_time', 'end_time', 'comments')
-        
+        self.fields.keyOrder = ('location', 'start_time',
+            'end_time', 'comments')
+
     def save(self, commit=True):
         entry = super(ClockOutForm, self).save(commit=False)
         entry.end_time = self.cleaned_data['end_time']
         if commit:
             entry.save()
         return entry
-        
+
+
 class AddUpdateEntryForm(forms.ModelForm):
     """
     This form will provide a way for users to add missed log entries and to
             date_format='%m/%d/%Y',
         )
     )
-    
+
     class Meta:
         model = Entry
         exclude = ('user', 'pause_time', 'site', 'hours', 'status',)
         self.fields['project'].queryset = timepiece.Project.objects.filter(
             users=self.user,
         )
-        if not self.instance.end_time:
-            del self.fields['end_time']
-            
+        #if editing a current entry, remove the end time field
+        if self.instance.start_time and not self.instance.end_time:
+            self.fields.pop('end_time')
+        #Use default activity if possible
         try:
             profile = self.user.profile
         except timepiece.UserProfile.DoesNotExist:
         else:
             if profile.default_activity:
                 self.fields['activity'].initial = profile.default_activity
+        self.instance.user = self.user
 
-    def clean_start_time(self):
+    def clean(self):
         """
-        Make sure that the start time is always before the end time
+        Verify that the entry doesn't conflict with or come after the current
+        entry, and that the times are valid for model clean
         """
-        start = self.cleaned_data['start_time']
+        cleaned_data = self.cleaned_data
+        start = cleaned_data.get('start_time', None)
+        end = cleaned_data.get('end_time', None)
+        if not start:
+            raise forms.ValidationError(
+                'Please enter a valid date/time.')
+        if start >= datetime.now() or end and end > datetime.now():
+            raise forms.ValidationError(
+                'Entries may not be added in the future.')
+        #Obtain all current entries, except the one being edited
+        times = [start, end] if end else [start]
+        query = reduce(lambda q, time: q | Q(start_time__lte=time), times, Q())
+        entries = self.user.timepiece_entries.filter(
+            query, end_time__isnull=True
+            ).exclude(id=self.instance.id)
+        for entry in entries:
+            output = 'The times below conflict with the current entry: ' + \
+            '%s - %s starting at %s' % \
+            (entry.project, entry.activity,
+                entry.start_time.strftime('%H:%M:%S'))
+            raise forms.ValidationError(output)
+        return self.cleaned_data
 
-        try:
-            end = self.cleaned_data['end_time']
+    def save(self, commit=True):
+        entry = super(AddUpdateEntryForm, self).save(commit=False)
+        entry.user = self.user
+        if commit:
+            entry.save()
+        return entry
 
-            if start >= end:
-                raise forms.ValidationError('The entry must start before it ends!')
-        except KeyError:
-            pass
 
-        if start > datetime.now():
-            raise forms.ValidationError('You cannot add entries in the future!')
+STATUS_CHOICES = [('', '---------'), ]
+STATUS_CHOICES.extend(timepiece.ENTRY_STATUS)
 
-        return start
-
-    def clean_end_time(self):
-        """
-        Make sure no one tries to add entries that end in the future
-        """
-        try:
-            start = self.cleaned_data['start_time']
-        except KeyError:
-            raise forms.ValidationError('Please enter a start time.')
-
-        try:
-            end = self.cleaned_data['end_time']
-            if not end: raise Exception
-        except:
-            raise forms.ValidationError('Please enter an end time.')
-
-        if start >= end:
-            raise forms.ValidationError('The entry must start before it ends!')
-
-        return end
-    
-    def save(self):
-        instance = super(AddUpdateEntryForm, self).save(commit=False)
-        instance.user = self.user
-        instance.save()
-        return instance
-        
-STATUS_CHOICES = [('','---------'),]
-STATUS_CHOICES.extend(timepiece.ENTRY_STATUS)
 
 class DateForm(forms.Form):
     from_date = forms.DateField(label="From", required=False)
     to_date = forms.DateField(label="To", required=False)
-    status = forms.ChoiceField(choices=STATUS_CHOICES, widget=forms.HiddenInput(), required=False)
+    status = forms.ChoiceField(choices=STATUS_CHOICES,
+        widget=forms.HiddenInput(), required=False)
     activity = forms.ModelChoiceField(
-        queryset=timepiece.Activity.objects.all(), 
+        queryset=timepiece.Activity.objects.all(),
         widget=forms.HiddenInput(), required=False,
     )
     project = forms.ModelChoiceField(
-        queryset=timepiece.Project.objects.all(), 
+        queryset=timepiece.Project.objects.all(),
         widget=forms.HiddenInput(), required=False,
     )
+
     def save(self):
         from_date = self.cleaned_data.get('from_date', '')
         to_date = self.cleaned_data.get('to_date', '')
 
 class ProjectionForm(DateForm):
     user = forms.ModelChoiceField(queryset=None)
-    
+
     def __init__(self, *args, **kwargs):
         users = kwargs.pop('users')
         super(ProjectionForm, self).__init__(*args, **kwargs)
         model = timepiece.Business
         fields = ('name', 'email', 'description', 'notes',)
 
+
 class ProjectForm(forms.ModelForm):
     class Meta:
         model = timepiece.Project
         self.fields['count'].required = False
         self.fields['interval'].required = False
         self.fields['date'] = forms.DateField(required=False)
-    
+
     def _clean_optional(self, name):
         active = self.cleaned_data.get('active', False)
         value = self.cleaned_data.get(name, '')
         if active and not value:
             raise forms.ValidationError('This field is required.')
         return self.cleaned_data[name]
-    
+
     def clean_count(self):
         return self._clean_optional('count')
-    
+
     def clean_interval(self):
         return self._clean_optional('interval')
-        
+
     def clean_date(self):
         active = self.cleaned_data.get('active', False)
         date = self.cleaned_data.get('date', '')
         if active and not self.instance.id and not date:
-            raise forms.ValidationError('Start date is required for new billing periods')
+            raise forms.ValidationError(
+                'Start date is required for new billing periods')
         return date
-    
+
     def clean(self):
         date = self.cleaned_data.get('date', '')
         if self.instance.id and date:
             latest = self.instance.billing_windows.latest()
             if self.cleaned_data['active'] and date < latest.end_date:
-                raise forms.ValidationError('New start date must be after %s' % latest.end_date)
+                raise forms.ValidationError(
+                    'New start date must be after %s' % latest.end_date)
         return self.cleaned_data
-    
+
     def save(self):
         period = super(RepeatPeriodForm, self).save(commit=False)
         if not self.instance.id and period.active:
     class Meta:
         model = timepiece.PersonRepeatPeriod
         fields = ('user',)
-    
+
     def __init__(self, *args, **kwargs):
         super(PersonTimeSheet, self).__init__(*args, **kwargs)
-        self.fields['user'].queryset = auth_models.User.objects.all().order_by('last_name')
+        self.fields['user'].queryset = \
+            auth_models.User.objects.all().order_by('last_name')
 
 
 class UserForm(forms.ModelForm):
-    
+
     class Meta:
         model = auth_models.User
         fields = ('first_name', 'last_name', 'email')
-    
+
     def __init__(self, *args, **kwargs):
         super(UserForm, self).__init__(*args, **kwargs)
         for name in self.fields:
             self.fields[name].required = True
-    
-        
+
+
 class UserProfileForm(forms.ModelForm):
 
     class Meta:

timepiece/lookups.py

 
 
 class UserLookup(object):
-
-    def get_query(self,q,request):
-        """ return a query set.  you also have access to request.user if needed """
+    def get_query(self, q, request):
+        """
+        return a query set.  you also have access to request.user if needed
+        """
         return auth_models.User.objects.filter(
-            Q(first_name__icontains=q) | 
+            Q(first_name__icontains=q) |
             Q(last_name__icontains=q) |
             Q(email__icontains=q)
         ).select_related().order_by('last_name')[:10]
-        
-    def format_item(self,user):
-        """ simple display of an object when it is displayed in the list of selected objects """
+
+    def format_item(self, user):
+        """
+        simple display of an object when it is displayed in the list of
+        selected objects
+        """
         return unicode(user)
 
-    def format_result(self,user):
-        """ a more verbose display, used in the search results display.  may contain html and multi-lines """
-        return u"<span class='individual'>%s %s</span>" % (user.first_name, user.last_name)
+    def format_result(self, user):
+        """
+        a more verbose display, used in the search results display.
+        may contain html and multi-lines
+        """
+        return u"<span class='individual'>%s %s</span>" % \
+        (user.first_name, user.last_name)
 
-    def get_objects(self,ids):
-        """ given a list of ids, return the objects ordered as you would like them on the admin page.
-            this is for displaying the currently selected items (in the case of a ManyToMany field)
+    def get_objects(self, ids):
+        """
+        given a list of ids, return the objects ordered as you would like them
+        on the admin page. This is for displaying the currently selected items
+        (in the case of a ManyToMany field)
         """
         return auth_models.User.objects.filter(pk__in=ids)
 
         self.pk = "%s-%d" % (type, pk)
         self.type = type
         self.name = name
-        
+
 
 class QuickLookup(object):
-
-    def get_query(self,q,request):
-        """ 
-        return a query set (or a fake one).  you also have access to request.user if needed 
+    def get_query(self, q, request):
+        """
+        return a query set (or a fake one).  you also have access to
+        request.user if needed
         """
         results = []
         users = auth_models.User.objects.filter(
-            Q(first_name__icontains=q) | 
+            Q(first_name__icontains=q) |
             Q(last_name__icontains=q) |
             Q(email__icontains=q)
         ).select_related().order_by('last_name')[:10]
             results.append(
                 SearchResult(business.pk, 'business', business.name)
             )
-        results.sort(lambda a,b: cmp(a.name, b.name))
+        results.sort(lambda a, b: cmp(a.name, b.name))
         return results
-        
+
     def format_item(self, item):
-        """ simple display of an object when it is displayed in the list of selected objects """
+        """
+        simple display of an object when it is displayed in the list of
+        selected objects
+        """
         return item.name
 
     def format_result(self, item):
-        """ a more verbose display, used in the search results display.  may contain html and multi-lines """
+        """
+        a more verbose display, used in the search results display.
+        may contain html and multi-lines
+        """
         return u"<span class='%s'>%s</span>" % (item.type, item.name)
 
     def get_objects(self, ids):
-        """ given a list of ids, return the objects ordered as you would like them on the admin page.
-            this is for displaying the currently selected items (in the case of a ManyToMany field)
+        """
+        given a list of ids, return the objects ordered as you would like them
+        on the admin page. this is for displaying the currently selected items
+        (in the case of a ManyToMany field)
         """
         results = []
         for id in ids:
                 results.append(timepiece.Business.objects.get(pk=pk))
             elif type == 'individual':
                 results.append(auth_models.User.objects.get(pk=pk))
-
         return results

timepiece/management/commands/check_entries.py

 
 
 class Command(BaseCommand):
+    """
+    Management command to check entries for overlapping times.
+    
+    Use ./manage.py check_entries --help for more details
+    """
+    #boiler plate for console programs using optparse
     args = '<user\'s first or last name or user.id> <user\'s first...>...'
-    help = 'Check the database for entries that overlap. Use --help for options'
-    
+    help = """Check the database for entries that overlap.
+    Use --help for options"""
     parser = OptionParser()
     parser.usage += """
-        To check all users:
-        ./manage.py check_entries [OPTIONS]
-        or
-        ./manage.py check_entries <first or last name1> <name2>...<name n> [OPTIONS]
-        
-        For options type:
-        ./manage.py check_entries --help
+./manage.py check_entries [<first or last name1> <name2>...<name n>] [OPTIONS]
+
+For options type:
+./manage.py check_entries --help
     """
-    option_list = BaseCommand.option_list + (
+
+    def make_options(self, *args, **kwargs):
+        """
+        Define the arguments that can be used with this command
+        """
+        return (
         make_option('--thisweek',
             action='store_true',
             dest='week',
             default=False,
             help='Show entries from this week only'),
-        ) +(
+        ) + (
         make_option('--thismonth',
             action='store_true',
-            dest = 'month',
-            default = False,
+            dest='month',
+            default=False,
             help='Show entries from this month only'),
-        ) +(
+        ) + (
         make_option('-y', '--thisyear',
             action='store_true',
-            dest = 'year',
-            default = False,
-            help = 'Show entries from this year only'),
-        ) +(
+            dest='year',
+            default=False,
+            help='Show entries from this year only'),
+        ) + (
         make_option('-a', '--all', '--forever',
             action='store_true',
-            dest = 'all',
-            default= False,
-            help = 'Show entries from all recorded history'),
-        )+(
+            dest='all',
+            default=False,
+            help='Show entries from all recorded history'),
+        ) + (
         make_option('-d', '--days',
-            dest = 'days',
-            type = 'int',
-            default = 0,
+            dest='days',
+            type='int',
+            default=0,
             help='Show entries for the last n days only'),
         )
+
+    option_list = BaseCommand.option_list + make_options(*args)
     parser.add_options(option_list)
-    (options, args) = parser.parse_args()  
+    (options, args) = parser.parse_args()
 
-    def handle(self, *args, **options):
-            
-        #If no flags, set to 3 months ago
+    def handle(self, *args, **kwargs):
+        """
+        main()
+        """
+        self.verbosity = kwargs.get('verbosity', 1)
+        start = self.find_start(**kwargs)
+        people = self.find_people(*args)
+        self.show_init(start, *args, **kwargs)
+        all_entries = self.find_entries(people, start, *args, **kwargs)
+        all_overlaps = self.check_all(all_entries, *args, **kwargs)
+        if self.verbosity >= 1:
+            self.stdout.write('Total overlapping entries: %d\n' % all_overlaps)
+
+    def check_all(self, all_entries, *args, **kwargs):
+        """
+        Go through lists of entries, find overlaps among each, return the total
+        """
+        all_overlaps = 0
+        while True:
+            try:
+                person_entries = all_entries.next()
+            except StopIteration:
+                return all_overlaps
+            else:
+                user_total_overlaps = self.check_entry(
+                    person_entries, *args, **kwargs)
+                all_overlaps += user_total_overlaps
+
+    def check_entry(self, entries, *args, **kwargs):
+        """
+        With a list of entries, check each entry against every other
+        """
+        user_total_overlaps = 0
+        user = ''
+        for index_a, entry_a in enumerate(entries):
+            #Show the name the first time through
+            if index_a == 0:
+                if args and self.verbosity >= 1 or self.verbosity >= 2:
+                    self.show_name(entry_a.user)
+                    user = entry_a.user
+            #Check against entries after this one
+            for index_b in range(index_a, len(entries)):
+                entry_b = entries[index_b]
+                if entry_a.check_overlap(entry_b):
+                    user_total_overlaps += 1
+                    self.show_overlap(entry_a, entry_b)
+        if user_total_overlaps and user and self.verbosity >= 1:
+            overlap_data = {
+                'first': user.first_name,
+                'last': user.last_name,
+                'total': user_total_overlaps,
+            }
+            self.stdout.write('Total overlapping entries for user ' + \
+                '%(first)s %(last)s: %(total)d\n' % overlap_data)
+        return user_total_overlaps
+
+    def find_start(self, **kwargs):
+        """
+        Determine the starting point of the query using CLI keyword arguments
+        """
+        week = kwargs.get('week', False)
+        month = kwargs.get('month', False)
+        year = kwargs.get('year', False)
+        days = kwargs.get('days', 0)
+        #If no flags are True, set to 2 months ago
         start = datetime.datetime.now() - datetime.timedelta(weeks=8)
         #Set the start date based on arguments provided through options
-        if self.options.week:
+        if week:
             start = utils.get_week_start()
-        if self.options.month:
+        if month:
             start = datetime.datetime.now() - relativedelta(day=1)
-        if self.options.year:
+        if year:
             start = datetime.datetime.now() - relativedelta(day=1, month=1)
-        if self.options.days:
-            start = datetime.datetime.now() - datetime.timedelta(days=self.options.days)
-            
-        if self.options.all:
-            print("Checking overlaps from the beginning of time")
-        else:    
-            print("Checking overlap starting at: " + str(start))     
-        
+        if days:
+            start = datetime.datetime.now() - \
+            datetime.timedelta(days=self.options.days)
+        start = start - relativedelta(
+            hour=0, minute=0, second=0, microsecond=0)
+        return start
+
+    def find_people(self, *args):
+        """
+        Returns the users to search given names as args. 
+        Return all users if there are no args provided.
+        """
+        if args:
+            names = reduce(lambda query, arg: query |
+                (Q(first_name__icontains=arg) | Q(last_name__icontains=arg)),
+                args, Q())
+            people = auth_models.User.objects.filter(names)
         #If no args given, check every user
-        if args: 
-            all_flag = False
-            names = reduce(lambda query, arg: query | (Q(first_name__icontains=arg) | Q(last_name__icontains=arg)), args, Q())                        
-            people = auth_models.User.objects.filter(names)
         else:
-            all_flag = True
-            people = auth_models.User.objects.all()            
-            
-        if not people.count() and not all_flag:
-            print("No user found with that name")
-            quit()
+            people = auth_models.User.objects.all()
+        #Display errors if no user was found
+        if not people.count() and args:
+            if len(args) == 1:
+                raise CommandError('No user was found with the name %s' \
+                % args[0])
+            else:
+                arg_list = ', '.join(args)
+                raise CommandError('No users found with the names: %s' \
+                % arg_list)
+        return people
 
-        for person in people:                        
-            if self.options.all:
-                entries = timepiece.Entry.objects.filter(user=person).order_by('start_time')
+    def find_entries(self, people, start, *args, **kwargs):
+        """
+        Find all entries for all users, from a given starting point.
+        If no starting point is provided, all entries are returned.
+        """
+        forever = kwargs.get('all', False)
+        for person in people:
+            if forever:
+                entries = timepiece.Entry.objects.filter(
+                    user=person).order_by(
+                    'start_time')
             else:
-                entries = timepiece.Entry.objects.filter(user=person, start_time__gte=start).order_by('start_time')
-                           
-            if not entries.count() or not all_flag: 
-                print('Checking %s %s...') % (person.first_name, person.last_name)
-            
-            for entry in entries:                   
-                if entry.is_overlapping():
-                    data = {
-                        'first_name':person.first_name, 'last_name':person.last_name, 
-                        'entry':entry.id, 
-                        'start_time':entry.start_time, 'end_time':entry.end_time,
-                        'project':entry.project
-                    }
-                    print(output(data))
+                entries = timepiece.Entry.objects.filter(
+                    user=person, start_time__gte=start).order_by(
+                    'start_time')
+            yield entries
 
+    #output methods
+    def show_init(self, start, *args, **kwargs):
+        forever = kwargs.get('all', False)
+        if forever:
+            if self.verbosity >= 1:
+                self.stdout.write('Checking overlaps from the beginning ' + \
+                    'of time\n')
+        else:
+            if self.verbosity >= 1:
+                self.stdout.write('Checking overlap starting on: ' + \
+                    start.strftime('%m/%d/%Y') + '\n')
 
-def output(data):
-    return "Entry %(entry)d for %(first_name)s %(last_name)s from %(start_time)s to %(end_time)s on %(project)s overlaps another entry" % data
-                       
-           
-                 
+    def show_name(self, person):
+        self.stdout.write('Checking %s %s...\n' % \
+        (person.first_name, person.last_name))
+
+    def show_overlap(self, entry_a, entry_b):
+        def make_output_data(entry):
+            return{
+                'first_name': entry.user.first_name,
+                'last_name': entry.user.last_name,
+                'entry': entry.id,
+                'start_time': entry.start_time,
+                'end_time': entry.end_time,
+                'project': entry.project
+            }
+        data_a = make_output_data(entry_a)
+        data_b = make_output_data(entry_b)
+        output = 'Entry %(entry)d for %(first_name)s %(last_name)s from ' \
+        % data_a + '%(start_time)s to %(end_time)s on %(project)s overlaps ' \
+        % data_a + 'entry %(entry)d from %(start_time)s to %(end_time)s on ' \
+        % data_b + '%(project)s.' % data_b
+        self.stdout.write(output + '\n')

timepiece/management/commands/update_billing_windows.py

 
 class Command(NoArgsCommand):
     help = "Generate billing windows"
-    
+
     @transaction.commit_on_success
     def handle_noargs(self, **options):
         output = []
-        
+
         projects = timepiece.Project.objects.filter(
             billing_period__active=True
         ).select_related(
                     'project_time_sheet',
                     args=(project.id, window.id),
                 )
-                urls.append(settings.APP_URL_BASE+url)
+                urls.append(settings.APP_URL_BASE + url)
             if urls:
                 output.append((project.name, urls))
         if output:
             print 'Project Billing Windows:\n'
             pprint.pprint(output)
-        
+
         output = []
         prps = timepiece.PersonRepeatPeriod.objects.filter(
             repeat_period__active=True,
-        ).select_related(    
+        ).select_related(
             'user',
             'repeat_period',
         )
                     'view_person_time_sheet',
                     args=(prp.user.id, prp.repeat_period.id, window.id),
                 )
-                urls.append(settings.APP_URL_BASE+url)
+                urls.append(settings.APP_URL_BASE + url)
             if urls:
                 output.append((prp.user.get_full_name(), urls))
-        
+
         if output:
             print '\nPerson Time Sheets:\n'
             pprint.pprint(output)

timepiece/models.py

         ('project-type', 'Project Type'),
         ('project-status', 'Project Status'),
     )
-    SORT_ORDER_CHOICES = [(x,x) for x in xrange(-20,21)]
+    SORT_ORDER_CHOICES = [(x, x) for x in xrange(-20, 21)]
     type = models.CharField(max_length=32, choices=ATTRIBUTE_TYPES)
     label = models.CharField(max_length=255)
     sort_order = models.SmallIntegerField(
         blank=True,
         choices=SORT_ORDER_CHOICES,
     )
-    enable_timetracking = models.BooleanField(default=False, 
+    enable_timetracking = models.BooleanField(default=False,
         help_text='Enable time tracking functionality for projects with this '
                   'type or status.',
     )
         return self.name
 
     class Meta:
-        ordering = ('name',)    
-    
+        ordering = ('name',)
+
+
 class Project(models.Model):
-    name = models.CharField(max_length = 255)
+    name = models.CharField(max_length=255)
     trac_environment = models.CharField(max_length=255, blank=True, null=True)
     business = models.ForeignKey(
         Business,
 class RelationshipType(models.Model):
     name = models.CharField(max_length=255, unique=True)
     slug = models.CharField(max_length=255, unique=True, editable=False)
-    
+
     def save(self):
         queryset = RelationshipType.objects.all()
         if self.id:
             queryset = queryset.exclude(id__exact=self.id)
         self.slug = utils.slugify_uniquely(self.name, queryset, 'slug')
         super(RelationshipType, self).save()
-    
+
     def __unicode__(self):
         return self.name
 
 
-class ProjectRelationship(models.Model): 
+class ProjectRelationship(models.Model):
     types = models.ManyToManyField(
         RelationshipType,
         related_name='project_relationships',
     code = models.CharField(
         max_length=5,
         unique=True,
-        help_text="""Enter a short code to describe the type of activity that took place."""
+        help_text='Enter a short code to describe the type of ' + \
+            'activity that took place.'
     )
     name = models.CharField(
         max_length=50,
         help_text="""Now enter a more meaningful name for the activity.""",
     )
     billable = models.BooleanField(default=True)
-    
+
     def __unicode__(self):
         return self.name
 
     ('invoiced', 'Invoiced',),
 )
 
+
 class Entry(models.Model):
     """
     This class is where all of the time logs are taken care of
     pause_time = models.DateTimeField(blank=True, null=True)
     comments = models.TextField(blank=True)
     date_updated = models.DateTimeField(auto_now=True)
-   
+
     hours = models.DecimalField(max_digits=8, decimal_places=2, default=0)
 
     objects = EntryManager()
     worked = EntryWorkedManager()
 
-    def is_overlapping(self):            
+    def check_overlap(self, entry_b):
+        """
+        Given two entries, return True if they overlap, otherwise return False
+        """
+        entry_a = self
+        #if entries are open, consider them closed right now
+        if not entry_a.end_time:
+            entry_a.end_time = datetime.datetime.now()
+        if not entry_b.end_time:
+            entry_b.end_time = datetime.datetime.now()
+        #Check the two entries against each other
+        if entry_a.start_time > entry_b.start_time \
+        and entry_a.start_time < entry_b.end_time or \
+        entry_a.end_time > entry_b.start_time \
+        and entry_a.end_time < entry_b.end_time or \
+        entry_a.start_time < entry_b.start_time \
+        and entry_a.end_time > entry_b.end_time:
+            return True
+        return False
 
+    def is_overlapping(self):
         if self.start_time and self.end_time:
             entries = self.user.timepiece_entries.filter(
-            Q(end_time__range=(self.start_time,self.end_time))|\
-            Q(start_time__range=(self.start_time,self.end_time))|\
+            Q(end_time__range=(self.start_time, self.end_time)) | \
+            Q(start_time__range=(self.start_time, self.end_time)) | \
             Q(start_time__lte=self.start_time, end_time__gte=self.end_time))
-                        
+
             totals = entries.aggregate(
-            max=Max('end_time'),min=Min('start_time'))
-            
+            max=Max('end_time'), min=Min('start_time'))
+
             totals['total'] = 0
             for entry in entries:
                 totals['total'] = totals['total'] + entry.get_seconds()
-            
-            totals['diff'] = totals['max']-totals['min']
-            totals['diff'] = totals['diff'].seconds + totals['diff'].days*86400
-            
+
+            totals['diff'] = totals['max'] - totals['min']
+            totals['diff'] = totals['diff'].seconds + \
+                totals['diff'].days * 86400
+
             if totals['total'] > totals['diff']:
                 return True
             else:
                 return False
         else:
-            return None    
-        
+            return None
+
     def clean(self):
-        if not self.user_id: return True
+        if not self.user_id:
+            raise ValidationError('An unexpected error has occured')
+        if not self.start_time:
+            raise ValidationError('Please enter a valid start time')
         start = self.start_time
         if self.end_time:
             end = self.end_time
         #Current entries have no end_time
         else:
-            end = start + datetime.timedelta(seconds=1)    
-        
+            end = start + datetime.timedelta(seconds=1)
         entries = self.user.timepiece_entries.filter(
-        Q(end_time__range=(start, end))|\
-        Q(start_time__range=(start, end))|\
-        Q(start_time__lte=start, end_time__gte=end)|\
-        Q(start_time__gt=start, end_time__isnull=True))#before current entry
-        
+            Q(end_time__range=(start, end)) | \
+            Q(start_time__range=(start, end)) | \
+            Q(start_time__lte=start, end_time__gte=end))
         #An entry can not conflict with itself so remove it from the list
-        if self.id: entries = entries.exclude(pk = self.id)
-        if len(entries):  
-            entry = entries[0]
+        if self.id:
+            entries = entries.exclude(pk=self.id)
+        for entry in entries:
             entry_data = {
                 'project': entry.project,
-                'activity' : entry.activity,
-                'start_time' : entry.start_time,
-                'end_time' : entry.end_time
+                'activity': entry.activity,
+                'start_time': entry.start_time,
+                'end_time': entry.end_time
             }
             #Conflicting saved entries
-            if entry.end_time:             
-                if entry.start_time.date() == start.date() and entry.end_time.date() == end.date():
-                    entry_data['start_time'] = entry.start_time.strftime('%H:%M:%S')
-                    entry_data['end_time'] = entry.end_time.strftime('%H:%M:%S')                
-                    output = 'Start time overlaps with: %(project)s - %(activity)s' \
-                     ' - from %(start_time)s to %(end_time)s' % entry_data
-            #Conflicting active entries
-            else:
-                output = 'The start time is on or before the current entry: %s - %s starting at %s' % \
-                    (entry.project, entry.activity, entry.start_time.time())
-            raise ValidationError(output)
-            
+            if entry.end_time:
+                if entry.start_time.date() == start.date() \
+                and entry.end_time.date() == end.date():
+                    entry_data['start_time'] = entry.start_time.strftime(
+                        '%H:%M:%S')
+                    entry_data['end_time'] = entry.end_time.strftime(
+                        '%H:%M:%S')
+                    output = 'Start time overlaps with: ' + \
+                    '%(project)s - %(activity)s - ' % entry_data + \
+                    'from %(start_time)s to %(end_time)s' % entry_data
+                    raise ValidationError(output)
+                else:
+                    entry_data['start_time'] = entry.start_time.strftime(
+                        '%H:%M:%S on %m\%d\%Y')
+                    entry_data['end_time'] = entry.end_time.strftime(
+                        '%H:%M:%S on %m\%d\%Y')
+                    output = 'Start time overlaps with: ' + \
+                    '%(project)s - %(activity)s - ' % entry_data + \
+                    'from %(start_time)s to %(end_time)s' % entry_data
+                    raise ValidationError(output)
+
         if end <= start:
-            raise ValidationError('Ending time must exceed the starting time')            
-        return True        
-           
+            raise ValidationError('Ending time must exceed the starting time')
+        return True
+
     def save(self, **kwargs):
         self.hours = Decimal('%.2f' % round(self.total_hours, 2))
-        super(Entry, self).save(**kwargs)        
-        
+        super(Entry, self).save(**kwargs)
+
     def get_seconds(self):
         """
         Determines the difference between the starting and ending time.  The
         """
         total = self.get_seconds() / 3600.0
         #in case seconds paused are greater than the elapsed time
-        if total < 0: total = 0
+        if total < 0:
+            total = 0
         return total
     total_hours = property(__total_hours)
 
     def pause(self):
         """
         If this entry is not paused, pause it.
-        """       
+        """
         if not self.is_paused:
             self.pause_time = datetime.datetime.now()
 
     def pause_all(self):
         """
         Pause all open entries
-        """        
+        """
         entries = self.user.timepiece_entries.filter(
         end_time__isnull=True).all()
         for entry in entries:
             self.user = user
             self.project = project
             if not self.start_time:
-                self.start_time = datetime.datetime.now()                
+                self.start_time = datetime.datetime.now()
 
     def __billing_window(self):
         return BillingWindow.objects.get(
             period__users=self.user,
-            date__lte = self.end_time,
-            end_date__gt = self.end_time)
+            date__lte=self.end_time,
+            end_date__gt=self.end_time)
     billing_window = property(__billing_window)
 
     def __is_editable(self):
         """
         Make it a little more interesting for deleting logs
         """
-        salt = '%i-%i-apple-%s-sauce' % (self.id, self.is_paused, self.is_closed)
+        salt = '%i-%i-apple-%s-sauce' \
+            % (self.id, self.is_paused, self.is_closed)
         try:
             import hashlib
         except ImportError:
 
 # Add a utility method to the User class that will tell whether or not a
 # particular user has any unclosed entries
-User.clocked_in = property(lambda user: user.timepiece_entries.filter(end_time__isnull=True).count() > 0)
+User.clocked_in = property(lambda user: user.timepiece_entries.filter(
+    end_time__isnull=True).count() > 0)
 
 
 class RepeatPeriodManager(models.Manager):
         )
         windows = []
         for period in active_billing_periods:
-            windows += ((period, period.update_billing_windows(date_boundary)),)
+            windows += ((period,
+                period.update_billing_windows(date_boundary)),)
         return windows
 
 
         ('year', 'Year(s)'),
     )
     count = models.PositiveSmallIntegerField(
-        choices=[(x,x) for x in range(1,32)],
+        choices=[(x, x) for x in range(1, 32)],
     )
     interval = models.CharField(
         max_length=10,
         else:
             return []
 
+
 class BillingWindow(models.Model):
     period = models.ForeignKey(RepeatPeriod, related_name='billing_windows')
     date = models.DateField()
 
     def __entries(self):
             return Entry.objects.filter(
-                end_time__lte = self.end_date,
-                end_time__gt = self.date)
+                end_time__lte=self.end_date,
+                end_time__gt=self.date)
     entries = property(__entries)
 
+
 class PersonRepeatPeriod(models.Model):
     user = models.ForeignKey(
         User,
     def hours_in_week(self, date):
         left, right = utils.get_week_window(date)
         entries = Entry.worked.filter(user=self.user)
-        entries = entries.filter(end_time__gt=left, end_time__lt=right, status='approved')
+        entries = entries.filter(end_time__gt=left,
+            end_time__lt=right, status='approved')
         return entries.aggregate(s=Sum('hours'))['s']
 
     def overtime_hours_in_week(self, date):
             data['paid_leave'][name] = qs.aggregate(s=Sum('hours'))['s']
         return data
 
-    def list_total_hours(self, N = 2):
-        bw = BillingWindow.objects.filter(period=self.repeat_period).order_by('-date')[:N]
+    def list_total_hours(self, N=2):
+        bw = BillingWindow.objects.filter(period=self.repeat_period).order_by(
+            '-date')[:N]
         result = []
         for b in bw:
             result.append(self.user.timepiece_entries.filter(
-                end_time__lte = b.end_date,
-                end_time__gt = b.date
+                end_time__lte=b.end_date,
+                end_time__gt=b.date
             ).aggregate(total=Sum('hours')))
         return result
+
     class Meta:
         permissions = (
             ('view_person_time_sheet', 'Can view person\'s timesheet.'),
             ('edit_person_time_sheet', 'Can edit person\'s timesheet.'),
         )
 
+
 class ProjectContract(models.Model):
     CONTRACT_STATUS = (
         ('upcoming', 'Upcoming'),
 
     @property
     def hours_allocated(self):
-        allocations = AssignmentAllocation.objects.filter(assignment__contract=self)
+        allocations = AssignmentAllocation.objects.filter(
+            assignment__contract=self)
         return allocations.aggregate(sum=Sum('hours'))['sum']
 
     @property
 class AssignmentManager(models.Manager):
     def active_during_week(self, week, next_week):
         q = Q(contract__end_date__gte=week, contract__end_date__lt=next_week)
-        q |= Q(contract__start_date__gte=week, contract__start_date__lt=next_week)
+        q |= Q(contract__start_date__gte=week,
+            contract__start_date__lt=next_week)
         q |= Q(contract__start_date__lt=week, contract__end_date__gt=next_week)
         return self.get_query_set().filter(q)
 
     def sort_by_priority(self):
-        return sorted(self.get_query_set().all(), key=lambda contract: contract.this_weeks_priority_number)
+        return sorted(self.get_query_set().all(),
+            key=lambda contract: contract.this_weeks_priority_number)
 
 
 # contract assignment logger
         """
         if not hasattr(self, '_priority_type'):
             weeks = utils.get_week_window(datetime.datetime.now())
-            if self.end_date < weeks[1].date() and self.end_date >= weeks[0].date():
+            if self.end_date < weeks[1].date() \
+            and self.end_date >= weeks[0].date():
                 self._priority_type = 0
-            elif self.start_date < weeks[1].date() and self.start_date >= weeks[0].date():
+            elif self.start_date < weeks[1].date() \
+            and self.start_date >= weeks[0].date():
                 self._priority_type = 1
             else:
                 self._priority_type = 2
 
     @property
     def this_weeks_priority_type(self):
-        type_list = ['ending', 'starting', 'ongoing',]
+        type_list = ['ending', 'starting', 'ongoing', ]
         return type_list[self.this_weeks_priority_number]
 
     def get_average_weekly_committment(self):
         week_start = utils.get_week_start()
         # calculate hours left on contract (subtract worked hours this week)
         remaining = self.num_hours - self._filtered_hours_worked(week_start)
-        commitment = remaining/self.contract.weeks_remaining.count()
+        commitment = remaining / self.contract.weeks_remaining.count()
         return commitment
 
     def weekly_commitment(self, day=None):
         self._log('Commitment after reservation {0}'.format(commitment))
         # if we're under the needed minimum hours and we have available
         # time, then raise our commitment to the desired level
-        if commitment < self.min_hours_per_week and unallocated >= self.min_hours_per_week:
+        if commitment < self.min_hours_per_week \
+        and unallocated >= self.min_hours_per_week:
             commitment = self.min_hours_per_week
-        self._log('Commitment after minimum weekly hours {0}'.format(commitment))
+        self._log('Commitment after minimum weekly hours {0}'\
+            .format(commitment))
         # calculate hours left on contract (subtract worked hours this week)
         week_start = utils.get_week_start(day)
         remaining = self.num_hours - self._filtered_hours_worked(week_start)
         return assignments.order_by('-end_date')
 
     def remaining_min_hours(self):
-        return self.remaining_contracts().aggregate(s=Sum('min_hours_per_week'))['s'] or 0
+        return self.remaining_contracts().aggregate(
+            s=Sum('min_hours_per_week'))['s'] or 0
 
     class Meta:
         unique_together = (('contract', 'user'),)
     def during_this_week(self, user, day=None):
         week = utils.get_week_start(day=day)
         return self.get_query_set().filter(
-            date=week, assignment__user=user, assignment__contract__status='current'
+            date=week, assignment__user=user,
+            assignment__contract__status='current'
             ).exclude(hours=0)
 
 
     @property
     def hours_available(self):
         today = datetime.date.today()
-        weeks_remaining = (self.end_date - today).days/7.0
+        weeks_remaining = (self.end_date - today).days / 7.0
         return float(self.hours_per_week) * weeks_remaining
 
     @property
 class UserProfile(models.Model):
     user = models.OneToOneField(User, unique=True, related_name='profile')
     default_activity = models.ForeignKey(Activity, blank=True, null=True)
-    
+
     def __unicode__(self):
         return unicode(self.user)

timepiece/projection.py

         for assignment in assignments:
             commitment = assignment.weekly_commitment(week)
             assignment.blocks.create(date=week, hours=commitment)
-            logger.debug('{0} | Commitment: {1:<6.2f} | {2}'.format(week, commitment, assignment))
-            
+            logger.debug('{0} | Commitment: {1:<6.2f} | {2}'.format(
+                week, commitment, assignment))
             # if hours_left <= 0:
             #     break
     logger.info('projection complete')
-

timepiece/templates/timepiece/time-sheet/people/view.html

 {% block content %}
 <h2>{{ person.first_name }} {{ person.last_name}}'s Time Sheet ({{ window.date|date:"N j" }} &mdash; {{ window.end_date|date:"N j" }})</h2>
 <ul class='header-actions-left'>
-    <li><a href='{% url view_person person.pk %}'>User Information</a></li>
+    <li>
+        {% if perms.timepiece.view_user %}
+            <a href='{% url view_person person.pk %}'>User Information</a>
+        {% endif %}
+    </li>
     <li>
         {% if window.previous %}
             <a href='{% url view_person_time_sheet person.pk,window.period.pk,window.previous.pk %}'>Previous Time Sheet</a>

timepiece/templatetags/math_tags.py

 
 register = template.Library()
 
+
 @register.filter
 def _abs(num):
     try:
 
 register.filter('abs', _abs)
 
+
 @register.filter
 def sub(num, arg):
     num = float(num)
     arg = float(arg)
-    return num-arg
+    return num - arg

timepiece/templatetags/timepiece_tags.py

 from timepiece.utils import generate_weeks, get_total_time
 
 
-
 register = template.Library()
 
 
 @register.filter
 def seconds_to_hours(seconds):
-    return round(seconds/3600.0, 2)
+    return round(seconds / 3600.0, 2)
 
 
 @register.inclusion_tag('timepiece/time-sheet/bar_graph.html',
     over_total = 0
     error = ''
     if worked < 0:
-        error = 'Somehow you\'ve logged %s negative hours for %s this week.' % (abs(worked), name)
+        error = 'Somehow you\'ve logged %s negative hours for %s this week.' \
+        % (abs(worked), name)
     if left < 0:
         over = abs(left)
         left = 0
         worked = total
         total = over + total
-    return { 
-        'name': name, 'worked': worked, 
-        'total': total, 'left':left,
-        'over': over, 'width': width, 
+    return {
+        'name': name, 'worked': worked,
+        'total': total, 'left': left,
+        'over': over, 'width': width,
         'suffix': suffix, 'error': error,
         }
 
                         takes_context=True)
 def my_ledger(context):
     try:
-        period = PersonRepeatPeriod.objects.get(user = context['request'].user)
+        period = PersonRepeatPeriod.objects.get(user=context['request'].user)
     except PersonRepeatPeriod.DoesNotExist:
-        return { 'period': False }
-    return { 'period': period }
+        return {'period': False}
+    return {'period': period}
 
 
-@register.inclusion_tag('timepiece/time-sheet/_date_filters.html', takes_context=True)
+@register.inclusion_tag('timepiece/time-sheet/_date_filters.html',
+    takes_context=True)
 def date_filters(context, options):
     request = context['request']
     from_slug = 'from_date'
     use_range = True
     if not options:
         options = ('months', 'quaters', 'years')
-    
+
     def construct_url(from_date, to_date):
         url = '%s?%s=%s' % (
             request.path,
     if 'months_no_range' in options:
         filters['Past 12 Months'] = []
         single_month = relativedelta(months=1)
-        from_date = datetime.date.today().replace(day=1) + relativedelta(months=1)
+        from_date = datetime.date.today().replace(day=1) + \
+            relativedelta(months=1)
         for x in range(12):
             to_date = from_date
             use_range = False
             from_date = to_date - single_month
-            url = construct_url(from_date,to_date - relativedelta(days=1))
-            filters['Past 12 Months'].append((from_date.strftime("%b '%y"), url))
-        filters['Past 12 Months'].reverse()        
-    
+            url = construct_url(from_date, to_date - relativedelta(days=1))
+            filters['Past 12 Months'].append(
+                (from_date.strftime("%b '%y"), url))
+        filters['Past 12 Months'].reverse()
+
     if 'months' in options:
         filters['Past 12 Months'] = []
         single_month = relativedelta(months=1)
-        from_date = datetime.date.today().replace(day=1) + relativedelta(months=1)
+        from_date = datetime.date.today().replace(day=1) + \
+            relativedelta(months=1)
         for x in range(12):
             to_date = from_date
             from_date = to_date - single_month
             url = construct_url(from_date, to_date - relativedelta(days=1))
-            filters['Past 12 Months'].append((from_date.strftime("%b '%y"), url))
+            filters['Past 12 Months'].append(
+                (from_date.strftime("%b '%y"), url))
         filters['Past 12 Months'].reverse()
-    
+
     if 'years' in options:
         start = datetime.date.today().year - 3
-        
+
         filters['Years'] = []
         for year in range(start, start + 3):
             from_date = datetime.datetime(year, 1, 1)
 @register.simple_tag
 def hours_for_assignment(assignment, date):
     end = date + relativedelta(days=5)
-    blocks = assignment.blocks.filter(date__gte=date, date__lte=end).select_related()
+    blocks = assignment.blocks.filter(
+        date__gte=date, date__lte=end).select_related()
     hours = blocks.aggregate(hours=Sum('hours'))['hours']
     if not hours:
         hours = ''
     return hours
-    
-    
+
+
 @register.simple_tag
 def total_allocated(assignment):
     hours = assignment.blocks.aggregate(hours=Sum('hours'))['hours']
 
 @register.simple_tag
 def week_start(date):
-    return get_week_start(date).strftime('%m/%d/%Y')  
-    
+    return get_week_start(date).strftime('%m/%d/%Y')
+
 
 @register.simple_tag
 def build_invoice_row(entries, to_date, from_date):
         else:
             uninvoiced_hours += entry['s']
     row = '<td>%s</td>' % uninvoiced_hours
-    url = reverse('export_project_time_sheet', args=[project,])
+    url = reverse('export_project_time_sheet', args=[project, ])
     to_date_str = from_date_str = ''
     if to_date:
         to_date_str = to_date.strftime('%m/%d/%Y')
     if from_date:
         from_date_str = from_date.strftime('%m/%d/%Y')
     get_str = urllib.urlencode({
-        'to_date': to_date_str, 
+        'to_date': to_date_str,
         'from_date': from_date_str,
         'status': 'approved',
     })
-    row += '<td><a href="#"><ul class="actions"><li><a href="%s?%s">CSV Timesheet</a></li>' % (url, get_str)
-    url = reverse('time_sheet_change_status', args=['invoice',])
+    row += '<td><a href="#"><ul class="actions"><li>' + \
+        '<a href="%s?%s">CSV Timesheet</a></li>' % (url, get_str)
+    url = reverse('time_sheet_change_status', args=['invoice', ])
     get_str = urllib.urlencode({
-        'to_date': to_date_str, 
+        'to_date': to_date_str,
         'from_date': from_date_str,
         'project': project,
     })