Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Import samples from internal git repo

  • Loading branch information...
commit b1895d3f9c9247a6fb48728d055395295c852d09 0 parents
@srid srid authored
Showing with 1,734 additions and 0 deletions.
  1. +19 −0 README.md
  2. +14 −0 README.txt
  3. 0  __init__.py
  4. 0  gtd/__init__.py
  5. +68 −0 gtd/admin.py
  6. 0  gtd/feeds.py
  7. 0  gtd/forms.py
  8. 0  gtd/management/__init__.py
  9. 0  gtd/management/commands/__init__.py
  10. +9 −0 gtd/management/commands/reminders.py
  11. +30 −0 gtd/managers.py
  12. +116 −0 gtd/migrations/0001_initial.py
  13. +78 −0 gtd/migrations/0002_auto__add_field_context_slug__add_field_context_parent__add_field_proj.py
  14. +65 −0 gtd/migrations/0003_auto__del_field_context_parent.py
  15. 0  gtd/migrations/__init__.py
  16. +245 −0 gtd/models.py
  17. +3 −0  gtd/templates/gtd/admin/reminders.html
  18. 0  gtd/templatetags/__init__.py
  19. +12 −0 gtd/tests.py
  20. +11 −0 gtd/urls.py
  21. +35 −0 gtd/utils.py
  22. 0  gtd/validators.py
  23. +6 −0 gtd/views/__init__.py
  24. 0  gtd/views/calendar.py
  25. +50 −0 gtd/views/dashboard.py
  26. +26 −0 gtd/views/objects.py
  27. 0  gtd/views/reports.py
  28. +11 −0 manage.py
  29. +3 −0  requirements.txt
  30. +128 −0 settings.py
  31. +18 −0 static/admin/css/changelist.css
  32. +53 −0 static/css/reset.css
  33. +483 −0 static/css/styles.css
  34. BIN  static/img/favicon.png
  35. BIN  static/img/sprite.png
  36. BIN  static/img/sprite.xcf
  37. 0  templates/gtd/context_detail.html
  38. 0  templates/gtd/context_list.html
  39. +139 −0 templates/gtd/dashboard.html
  40. 0  templates/gtd/project_detail.html
  41. 0  templates/gtd/project_list.html
  42. +21 −0 templates/gtd/status_icons.html
  43. 0  templates/gtd/thing_detail.html
  44. 0  templates/gtd/thing_list.html
  45. +17 −0 templates/gtd/thing_list_item.html
  46. +19 −0 templates/layouts/base.html
  47. +21 −0 templates/registration/login.html
  48. +26 −0 urls.py
  49. +8 −0 wsgi.py
19 README.md
@@ -0,0 +1,19 @@
+# Django GTD application
+
+## Local development
+
+ pypm install -r requirements.txt
+ python manage.py syncdb
+ python manage.py migrate
+ python manager.py runserver
+
+## Deploying to Stackato
+
+ stackato push django-gtd
+ stackato run "python manage.py syncdb --noinput"
+ stackato run "python manage.py migrate --noinput"
+
+### Limitations
+
+Django admin's superuser creation cannot be automated (note:
+``--noinput``), so /admin/ is unusable for this app.
14 README.txt
@@ -0,0 +1,14 @@
+Deploying to Stackato
+=====================
+
+1. ``vmc push djangogtd`` (add a mysql service)
+
+2. Initialize the db::
+
+ vmc run djangogtd "python manage.py syncdb --noinput"
+ vmc run djangogtd "python manage.py migrate --noinput"
+
+3. # XXX: can't run `manage.py createsuperuser --noinput` as it
+ doesn't set a password
+
+4. Open the djangogtd URL, you should see the app albeit with empty data.
0  __init__.py
No changes.
0  gtd/__init__.py
No changes.
68 gtd/admin.py
@@ -0,0 +1,68 @@
+from django.contrib import admin
+from django.contrib import databrowse
+from gtd.models import Thing, Project, Context, Reminder
+from django import forms
+
+class ReminderInline(admin.TabularInline):
+ model = Reminder
+ extra = 0
+
+class ThingAdmin(admin.ModelAdmin):
+ date_hierarchy = 'created'
+ list_display = ('get_name', 'get_context', 'get_project', 'get_progress',
+ 'status', 'get_reminders', 'get_created')
+ list_display_link = ('get_name', 'status')
+ list_filter = ('context', 'project', 'schedule','status', 'deleted', 'archived', )
+ list_editable = ('status', )
+ search_fields = ['name', 'description']
+ inlines = [ReminderInline,]
+ select_related = True
+
+ class Media:
+ css = {"all": ("admin/css/changelist.css",)}
+
+class ThingModelForm(forms.ModelForm):
+ class Meta:
+ model = Thing
+ fields = ('name', 'context', 'progress', 'status', 'project', 'deleted', 'archived')
+
+class ThingInline(admin.TabularInline):
+ model = Thing
+ form = ThingModelForm
+
+class ProjectAdmin(admin.ModelAdmin):
+ date_hierarchy = 'created'
+ list_display = ('name', 'active', 'thing_count', 'thing_todo_count', 'thing_done_count', 'get_progress_bar', 'get_deadline')
+ list_filter = ('active', 'deadline')
+ list_editable = ('active', )
+ search_fields = ['name', 'description']
+ inlines = [ThingInline,]
+ prepopulated_fields = {'slug': ('name',)}
+ fieldsets = (
+ (None, {
+ 'fields': ('name', 'slug', 'deadline', 'get_progress', 'description', 'active')
+ }),
+ )
+ readonly_fields = ['get_progress',]
+
+ class Media:
+ css = {"all": ("admin/css/changelist.css",)}
+
+class ContextAdmin(admin.ModelAdmin):
+ list_display = ('__unicode__', 'thing_count', 'thing_todo_count', 'thing_done_count')
+ prepopulated_fields = {'slug': ('name',)}
+ inlines = [ThingInline,]
+
+class ReminderAdmin(admin.ModelAdmin):
+ list_display = ('__unicode__', 'countdown')
+ list_filter = ('method',)
+ search_fields = ['thing__name', 'thing__description']
+
+admin.site.register(Thing, ThingAdmin)
+admin.site.register(Project, ProjectAdmin)
+admin.site.register(Context, ContextAdmin)
+admin.site.register(Reminder, ReminderAdmin)
+databrowse.site.register(Thing)
+databrowse.site.register(Project)
+databrowse.site.register(Context)
+databrowse.site.register(Reminder)
0  gtd/feeds.py
No changes.
0  gtd/forms.py
No changes.
0  gtd/management/__init__.py
No changes.
0  gtd/management/commands/__init__.py
No changes.
9 gtd/management/commands/reminders.py
@@ -0,0 +1,9 @@
+from django.core.management.base import BaseCommand, CommandError
+from gtd.models import Reminder
+
+class Command(BaseCommand):
+ help = 'Execute action reminders'
+
+ def handle(self, *args, **options):
+ for reminder in Reminder.objects.all():
+ print u'%s' % reminder
30 gtd/managers.py
@@ -0,0 +1,30 @@
+from django.db import models
+
+class FocusManager(models.Manager):
+ def get_query_set(self):
+ return super(FocusManager, self).get_query_set().filter(deleted=False)
+
+ def today(self, queryset):
+ pass
+
+ def tomorrow(self, queryset):
+ pass
+
+ def overdue(self, queryset):
+ pass
+
+ def executable(self, queryset):
+ pass
+
+ def following(self, queryset):
+ pass
+
+ def delegated(self, queryset):
+ pass
+
+ def deferred(self, queryset):
+ pass
+
+ def incubated(self, queryset):
+ pass
+
116 gtd/migrations/0001_initial.py
@@ -0,0 +1,116 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding model 'Project'
+ db.create_table('gtd_project', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=90)),
+ ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('active', self.gf('django.db.models.fields.BooleanField')(default=True, blank=True)),
+ ('deadline', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ))
+ db.send_create_signal('gtd', ['Project'])
+
+ # Adding model 'Context'
+ db.create_table('gtd_context', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=90)),
+ ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ))
+ db.send_create_signal('gtd', ['Context'])
+
+ # Adding model 'Thing'
+ db.create_table('gtd_thing', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('name', self.gf('django.db.models.fields.CharField')(max_length=140)),
+ ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+ ('status', self.gf('django.db.models.fields.IntegerField')(default=1)),
+ ('project', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gtd.Project'], null=True, blank=True)),
+ ('context', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gtd.Context'])),
+ ('deleted', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+ ('archived', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)),
+ ('progress', self.gf('django.db.models.fields.IntegerField')(default=0)),
+ ('schedule', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
+ ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+ ('modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
+ ))
+ db.send_create_signal('gtd', ['Thing'])
+
+ # Adding model 'Reminder'
+ db.create_table('gtd_reminder', (
+ ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+ ('thing', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gtd.Thing'])),
+ ('number', self.gf('django.db.models.fields.IntegerField')()),
+ ('unit', self.gf('django.db.models.fields.IntegerField')(default=3)),
+ ('method', self.gf('django.db.models.fields.IntegerField')(default=1)),
+ ))
+ db.send_create_signal('gtd', ['Reminder'])
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Project'
+ db.delete_table('gtd_project')
+
+ # Deleting model 'Context'
+ db.delete_table('gtd_context')
+
+ # Deleting model 'Thing'
+ db.delete_table('gtd_thing')
+
+ # Deleting model 'Reminder'
+ db.delete_table('gtd_reminder')
+
+
+ models = {
+ 'gtd.context': {
+ 'Meta': {'object_name': 'Context'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '90'})
+ },
+ 'gtd.project': {
+ 'Meta': {'object_name': 'Project'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'deadline': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '90'})
+ },
+ 'gtd.reminder': {
+ 'Meta': {'object_name': 'Reminder'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'method': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'number': ('django.db.models.fields.IntegerField', [], {}),
+ 'thing': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Thing']"}),
+ 'unit': ('django.db.models.fields.IntegerField', [], {'default': '3'})
+ },
+ 'gtd.thing': {
+ 'Meta': {'object_name': 'Thing'},
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'context': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Context']"}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
+ 'progress': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Project']", 'null': 'True', 'blank': 'True'}),
+ 'schedule': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+ }
+ }
+
+ complete_apps = ['gtd']
78 gtd/migrations/0002_auto__add_field_context_slug__add_field_context_parent__add_field_proj.py
@@ -0,0 +1,78 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'Context.slug'
+ db.add_column('gtd_context', 'slug', self.gf('django.db.models.fields.SlugField')(default='', max_length=50, db_index=True), keep_default=False)
+
+ # Adding field 'Context.parent'
+ db.add_column('gtd_context', 'parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gtd.Context'], null=True, blank=True), keep_default=False)
+
+ # Adding field 'Project.slug'
+ db.add_column('gtd_project', 'slug', self.gf('django.db.models.fields.SlugField')(default='', max_length=50, db_index=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Context.slug'
+ db.delete_column('gtd_context', 'slug')
+
+ # Deleting field 'Context.parent'
+ db.delete_column('gtd_context', 'parent_id')
+
+ # Deleting field 'Project.slug'
+ db.delete_column('gtd_project', 'slug')
+
+
+ models = {
+ 'gtd.context': {
+ 'Meta': {'object_name': 'Context'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '90'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Context']", 'null': 'True', 'blank': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
+ },
+ 'gtd.project': {
+ 'Meta': {'object_name': 'Project'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'deadline': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '90'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
+ },
+ 'gtd.reminder': {
+ 'Meta': {'object_name': 'Reminder'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'method': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'number': ('django.db.models.fields.IntegerField', [], {}),
+ 'thing': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Thing']"}),
+ 'unit': ('django.db.models.fields.IntegerField', [], {'default': '3'})
+ },
+ 'gtd.thing': {
+ 'Meta': {'object_name': 'Thing'},
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'context': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Context']"}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
+ 'progress': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Project']", 'null': 'True', 'blank': 'True'}),
+ 'schedule': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+ }
+ }
+
+ complete_apps = ['gtd']
65 gtd/migrations/0003_auto__del_field_context_parent.py
@@ -0,0 +1,65 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Deleting field 'Context.parent'
+ db.delete_column('gtd_context', 'parent_id')
+
+
+ def backwards(self, orm):
+
+ # Adding field 'Context.parent'
+ db.add_column('gtd_context', 'parent', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['gtd.Context'], null=True, blank=True), keep_default=False)
+
+
+ models = {
+ 'gtd.context': {
+ 'Meta': {'object_name': 'Context'},
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '90'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
+ },
+ 'gtd.project': {
+ 'Meta': {'object_name': 'Project'},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'deadline': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '90'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'db_index': 'True'})
+ },
+ 'gtd.reminder': {
+ 'Meta': {'object_name': 'Reminder'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'method': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
+ 'number': ('django.db.models.fields.IntegerField', [], {}),
+ 'thing': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Thing']"}),
+ 'unit': ('django.db.models.fields.IntegerField', [], {'default': '3'})
+ },
+ 'gtd.thing': {
+ 'Meta': {'object_name': 'Thing'},
+ 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'context': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Context']"}),
+ 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+ 'deleted': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
+ 'progress': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+ 'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['gtd.Project']", 'null': 'True', 'blank': 'True'}),
+ 'schedule': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'status': ('django.db.models.fields.IntegerField', [], {'default': '1'})
+ }
+ }
+
+ complete_apps = ['gtd']
0  gtd/migrations/__init__.py
No changes.
245 gtd/models.py
@@ -0,0 +1,245 @@
+from django.db import models
+from gtd import managers
+from django.core.exceptions import ValidationError
+from django.db.models import Sum
+from datetime import datetime
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.humanize.templatetags.humanize import naturalday
+from django.core.urlresolvers import reverse
+from django.template.loader import render_to_string
+
+class Project(models.Model):
+ """
+ Projects are a set of 'things' that could be tasks, this indentify the
+ progress as the sum of all task progress. So you can have a big picture of
+ the advance of all the set of task.
+
+ All projects are prefixed with '#'. A Project can have 'Things' (tasks)
+ with diferent contexts.
+ """
+ name = models.CharField(_('name'), max_length=90)
+ slug = models.SlugField(_('code'))
+ description = models.TextField(_('description'), blank=True)
+ active = models.BooleanField(_('active'), default=True)
+ deadline = models.DateTimeField(_('deadline'), blank=True, null=True)
+
+ created = models.DateTimeField(auto_now_add=True)
+ modified = models.DateTimeField(auto_now=True)
+
+ objects = models.Manager()
+
+ class Meta:
+ get_latest_by = 'created'
+ ordering = ['-created',]
+
+ def __unicode__(self):
+ return u"#%s" % self.slug
+
+ @property
+ def progress(self):
+ things = self.thing_set.filter(deleted=False).exclude(status=Thing.STATE_INCUBATE)
+ things_count = things.count()
+ if things_count > 0 :
+ return things.all().aggregate(Sum('progress'))['progress__sum'] / things_count
+ else:
+ return 0
+
+ def get_progress(self):
+ return u'%s%%' % (self.progress)
+ get_progress.short_description = 'Progress'
+
+ def get_progress_bar(self):
+ return u'<div class="progress_bar"><span style="width: %spx">%s%%</span></div>' % (self.progress, self.progress)
+ get_progress_bar.allow_tags = True
+ get_progress_bar.short_description = 'Progress Bar'
+
+ def thing_count(self):
+ return self.thing_set.filter(deleted=False).count()
+ thing_count.short_description = 'Things'
+
+ def thing_todo_count(self):
+ return self.thing_set.filter(status=Thing.STATE_ACTIONABLE, deleted=False).count()
+ thing_todo_count.short_description = 'Things to do'
+
+ def thing_done_count(self):
+ return self.thing_set.filter(status=Thing.STATE_DONE, deleted=False).count()
+ thing_done_count.short_description = 'Things done'
+
+ def get_deadline(self):
+ if self.deadline:
+ return naturalday(self.deadline)
+ else:
+ return u''
+ get_deadline.allow_tags = True
+ get_deadline.short_description = 'Deadline'
+
+class Context(models.Model):
+ """
+ Context is a categorization for task that describes if this is a tool,
+ location or person that is required to be able to complete an action.
+
+ Context are long term definitions, isn't the same as projects. A 'Thing'
+ can just have one context, and couldb be involve (or not) in one project.
+ A Project can have tasks in many context.
+ """
+ name = models.CharField(_('name'), max_length=90)
+ slug = models.SlugField(_('code'))
+ description = models.TextField(_('description'), blank=True)
+
+ class Meta:
+ ordering = ['slug']
+
+ def __unicode__(self):
+ return u"@%s" % self.slug
+
+ def thing_count(self):
+ return self.thing_set.filter(deleted=False).count()
+ thing_count.short_description = 'Things'
+
+ def thing_done_count(self):
+ return self.thing_set.filter(status=Thing.STATE_DONE, deleted=False).count()
+ thing_done_count.short_description = 'Things done'
+
+ def thing_todo_count(self):
+ return self.thing_set.filter(status=Thing.STATE_ACTIONABLE, deleted=False).count()
+ thing_todo_count.short_description = 'Things to do'
+
+def validate_progress(value):
+ if value > 100 or value < 0:
+ raise ValidationError(_(u'%s is not a valid progress value') % value)
+
+def validate_schedule(value):
+ if value < datetime.now() :
+ raise ValidationError(_(u'Schedule date must be in future'))
+
+class Thing(models.Model):
+ STATE_THING = 1
+ STATE_WAITING = 2
+ STATE_ACTIONABLE = 3
+ STATE_DELEGATED = 4
+ STATE_DEFERRED = 5
+ STATE_INCUBATE = 6
+ STATE_DONE = 7
+
+ STATES = (
+ (STATE_THING, _('thing')),
+ (STATE_WAITING, _('waiting')),
+ (STATE_ACTIONABLE, _('actionable')),
+ (STATE_DELEGATED, _('delegated')),
+ (STATE_DEFERRED, _('deferred')),
+ (STATE_INCUBATE, _('incubate')),
+ (STATE_DONE, _('done')),
+ )
+
+ name = models.CharField(_('name'), max_length=140)
+ description = models.TextField(_('description'), blank=True)
+ status = models.IntegerField(_('status'), choices=STATES, default=STATE_THING)
+ project = models.ForeignKey(Project, null=True, blank=True)
+ context = models.ForeignKey(Context)
+ deleted = models.BooleanField(_('deleted'), default=False)
+ archived = models.BooleanField(_('archived'), default=False)
+ progress = models.IntegerField(_('progress'), default=0, validators=[validate_progress])
+ schedule = models.DateTimeField(_('schedule'), null=True, blank=True)
+
+ created = models.DateTimeField(auto_now_add=True)
+ modified = models.DateTimeField(auto_now=True)
+
+ objects = models.Manager()
+ focus = managers.FocusManager()
+
+ class Meta:
+ get_latest_by = 'created'
+ ordering = ['-created',]
+
+ def __unicode__(self):
+ return u'%s : %s [%s]' % (self.context, self.name, self.get_status_display())
+
+ def get_name(self):
+ if self.deleted:
+ return u'<del>%s</del>' % self.name
+ elif self.status == self.STATE_DONE:
+ return u'<span class="thing_done">%s</span>' % self.name
+ else:
+ return u'%s' % self.name
+ get_name.allow_tags = True
+ get_name.short_description = 'Thing'
+
+ def get_project(self):
+ if self.project:
+ return u'<a href="%s">%s</a>' % (reverse('admin:gtd_project_change', args=[self.project.id]), self.project.name)
+ else:
+ return u''
+ get_project.allow_tags = True
+ get_project.short_description = 'Project'
+
+ def get_context(self):
+ return u'<a href="%s">%s</a>' % (reverse('admin:gtd_context_change', args=[self.context.id]), self.context.__unicode__())
+ get_context.allow_tags = True
+ get_context.short_description = 'Context'
+
+ def get_progress(self):
+ return u'<div class="progress_bar"><span style="width: %spx">%s%%</span></div>' % (self.progress, self.progress)
+ get_progress.allow_tags = True
+ get_progress.short_description = 'Progress'
+
+ def get_reminders(self):
+ return render_to_string('gtd/admin/reminders.html', dict(thing=self))
+ get_reminders.allow_tags = True
+ get_reminders.short_description = 'Reminders'
+
+ def get_created(self):
+ return naturalday(self.created)
+ get_created.allow_tags = True
+ get_created.short_description = 'Created'
+
+ def save(self, *args, **kwargs):
+ if self.schedule and not self.status in (self.STATE_DONE, ):
+ self.status = self.STATE_DEFERRED
+ if self.progress == 100:
+ self.status = self.STATE_DONE
+ elif self.status == self.STATE_DONE:
+ self.progress = 100
+
+ super(Thing, self).save(*args, **kwargs)
+
+class Reminder(models.Model):
+ UNIT_MINUTE = 1
+ UNIT_HOUR = 2
+ UNIT_DAY = 3
+
+ UNITS = (
+ (UNIT_MINUTE, _('minutes')),
+ (UNIT_HOUR, _('hours')),
+ (UNIT_DAY, _('days')),
+ )
+
+ METHOD_ALERT = 1
+ METHOD_EMAIL = 2
+
+ METHODS = (
+ (METHOD_ALERT, _('flash alert')),
+ (METHOD_EMAIL, _('email notification')),
+ )
+
+ thing = models.ForeignKey(Thing,
+ limit_choices_to = {'schedule__gte': datetime.now})
+ number = models.IntegerField()
+ unit = models.IntegerField(choices=UNITS, default=UNIT_DAY)
+ method = models.IntegerField(choices=METHODS, default=METHOD_ALERT)
+
+ def __unicode__(self):
+ return _(u'Remind "%s" %s %s before by %s') % (
+ self.thing,
+ self.number,
+ self.get_unit_display(),
+ self.get_method_display()
+ )
+
+ @property
+ def countdown(self):
+ return self.thing.schedule - datetime.now()
+
+ def clean(self):
+ if not self.thing.schedule:
+ raise ValidationError(_(u'Thing must have a schedule date to add a reminder'))
+
3  gtd/templates/gtd/admin/reminders.html
@@ -0,0 +1,3 @@
+{% for reminder in thing.reminder_set.all %}
+ <big><a href="{% url admin:gtd_reminder_change reminder.id %}" title="{{reminder}}">⚑ </a></big>
+{% endfor %}
0  gtd/templatetags/__init__.py
No changes.
12 gtd/tests.py
@@ -0,0 +1,12 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def setUp(self):
+ pass
11 gtd/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('gtd.views',
+ url(r'^$', 'dashboard.dashboard', name='dashboard'),
+ url(r'^thing/$', 'objects.thing_list', name='thing_list'),
+ url(r'^thing/(?P<id>\d+)$', 'objects.thing_detail', name='thing_detail'),
+ url(r'^context/$', 'objects.context_list', name='context_list'),
+ url(r'^context/(?P<id>\d+)$', 'objects.context_detail', name='context_detail'),
+ url(r'^project/$', 'objects.project_list', name='project_list'),
+ url(r'^project/(?P<id>\d+)$', 'objects.project_detail', name='project_detail'),
+)
35 gtd/utils.py
@@ -0,0 +1,35 @@
+from django.http import QueryDict
+from django.core.paginator import Paginator, InvalidPage, EmptyPage
+
+def query_dict(querystring, exclude):
+ qdict = QueryDict(querystring).copy()
+ query_string = str()
+
+ if type(exclude) != tuple:
+ raise Exception("exclude must be a tuple")
+
+ for item in exclude:
+ if item in qdict:
+ qdict.pop(item)
+
+ query_dict_urlencode = qdict.urlencode()
+
+ if query_dict_urlencode != '':
+ query_string = query_dict_urlencode + "&"
+
+ query_string.replace('&', '&amp;')
+
+ return query_string
+
+def paginate(obj, page, per_page=12):
+ paginator = Paginator(obj, per_page)
+
+ if not page:
+ page = 1
+
+ try:
+ object_list = paginator.page(page)
+ except (EmptyPage, InvalidPage):
+ object_list = paginator.page(paginator.num_pages)
+
+ return object_list
0  gtd/validators.py
No changes.
6 gtd/views/__init__.py
@@ -0,0 +1,6 @@
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+
+def render_response(request, template, context=None, mimetype='text/html'):
+ return render_to_response(template, context or {},
+ context_instance=RequestContext(request), mimetype=mimetype)
0  gtd/views/calendar.py
No changes.
50 gtd/views/dashboard.py
@@ -0,0 +1,50 @@
+from gtd.views import render_response
+from gtd.models import Thing, Context, Project
+from gtd.utils import query_dict, paginate
+from django.shortcuts import get_object_or_404
+
+def dashboard(request):
+ status = request.GET.get('status', None)
+ context_id = request.GET.get('context', None)
+ project_id = request.GET.get('project', None)
+ thing_list = Thing.objects.select_related().filter(deleted=False)
+
+ view_context = dict()
+
+ if status:
+ thing_list = thing_list.filter(status=status)
+ if int(status) == Thing.STATE_DEFERRED:
+ thing_list = thing_list.order_by('schedule')
+
+ if context_id:
+ context_id = int(context_id)
+ context = get_object_or_404(Context, pk=context_id)
+ thing_list = thing_list.filter(context=context_id)
+ view_context.update({'context': context})
+
+ if project_id:
+ project_id = int(project_id)
+ project = get_object_or_404(Project, pk=project_id)
+ thing_list = thing_list.filter(project=project_id)
+ view_context.update({'project': project})
+
+ things = paginate(thing_list, request.GET.get('page', None))
+
+ #TODO: Register as a template tag to choose exclude params in template
+ query_string = query_dict(request.GET.urlencode(), exclude=('page','context', 'project'))
+
+ projects = Project.objects.filter(active=True)
+ contexts = Context.objects.all()
+
+ view_context.update({
+ 'things': things,
+ 'projects': projects,
+ 'contexts': contexts,
+ 'query_string': query_string,
+ 'status': status,
+ 'project_id': project_id,
+ 'context_id': context_id
+ })
+
+ return render_response(request, 'gtd/dashboard.html', view_context)
+
26 gtd/views/objects.py
@@ -0,0 +1,26 @@
+from gtd.models import Thing
+from django.views.generic.list_detail import object_list, object_detail
+
+def thing_list(request):
+ thing_list = Thing.objects.all()
+ return object_list(request, queryset=thing_lists)
+
+def thing_detail(request, id):
+ thing_detail = Thing.objects.get(id)
+ return object_detail(request, queryset=thing_detail)
+
+def context_list(request):
+ context_list = Context.objects.all()
+ return object_list(request, queryset=context_lists)
+
+def context_detail(request, id):
+ context_detail = Context.objects.get(id)
+ return object_detail(request, queryset=context_detail)
+
+def project_list(request):
+ project_list = Project.objects.all()
+ return object_list(request, queryset=project_lists)
+
+def project_detail(request, id):
+ project_detail = Project.objects.get(id)
+ return object_detail(request, queryset=project_detail)
0  gtd/views/reports.py
No changes.
11 manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import 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(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
3  requirements.txt
@@ -0,0 +1,3 @@
+django
+mysql-python
+south
128 settings.py
@@ -0,0 +1,128 @@
+# Django settings for djangogtd project.
+import sys, os
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ROOT = os.path.abspath(os.path.dirname(__file__))
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+
+## Pull in CloudFoundry's production settings
+if 'VCAP_SERVICES' in os.environ:
+ import json
+ vcap_services = json.loads(os.environ['VCAP_SERVICES'])
+ # XXX: avoid hardcoding here
+ mysql_srv = vcap_services['mysql-5.1'][0]
+ cred = mysql_srv['credentials']
+ DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': cred['name'], # Or path to database file if using sqlite3.
+ 'USER': cred['user'], # Not used with sqlite3.
+ 'PASSWORD': cred['password'], # Not used with sqlite3.
+ 'HOST': cred['hostname'], # Set to empty string for localhost. Not used with sqlite3.
+ 'PORT': cred['port'], # Set to empty string for default. Not used with sqlite3.
+ }
+ }
+else:
+ DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': '/tmp/djangogtd.sqlite3', # 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/Vancouver'
+
+# 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 path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = os.path.join(ROOT, 'static')
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/static/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/admin/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '77777777777777777777777777777u9w@hs7bdxt+w1k@f2&di'
+
+# 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',
+)
+
+IGNORABLE_404_STARTS = ('/cgi-bin/', '/_vti_bin', '/_vti_inf')
+
+IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'favicon.ico', '.php')
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+ os.path.join(ROOT, 'templates'),
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ # 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.admin',
+ 'django.contrib.admindocs',
+ 'django.contrib.databrowse',
+ 'django.contrib.humanize',
+ 'gtd',
+ 'south',
+)
+
+# try:
+# execfile(os.path.join(ROOT, 'localsettings.py'))
+# except:
+# sys.stderr.write("Error: Cant' find the file 'localsettings.py'. Please"
+# "override the settings localy not in settings.py \n")
18 static/admin/css/changelist.css
@@ -0,0 +1,18 @@
+.progress_bar {
+ border: 1px solid #CCC;
+ width: 100px;
+ height: 12px;
+}
+
+.progress_bar span {
+ height: 12px;
+ background-color: #CCC;
+ display: block;
+ width: 0px;
+ text-align: center;
+ font-size: .8em;
+}
+
+.thing_done {
+ font-weight: normal;
+}
53 static/css/reset.css
@@ -0,0 +1,53 @@
+/*
+ Name: Reset Stylesheet
+ Description: Resets browser's default CSS
+ Author: Eric Meyer
+ Author URI: http://meyerweb.com/eric/tools/css/reset/
+*/
+
+/* v1.0 | 20080212 */
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+ background: transparent;
+ border: 0;
+ font-size: 100%;
+ margin: 0;
+ outline: 0;
+ padding: 0;
+ vertical-align: baseline;
+}
+
+body {line-height: 1;}
+
+ol, ul {list-style: none;}
+
+blockquote, q {quotes: none;}
+
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+
+/* remember to define focus styles! */
+:focus {
+ outline: 0;
+}
+
+/* remember to highlight inserts somehow! */
+ins {text-decoration: none;}
+del {text-decoration: line-through;}
+
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
483 static/css/styles.css
@@ -0,0 +1,483 @@
+@import url("reset.css");
+body, html {
+ font-family: "lucida grande", tahoma, verdana, arial, sans-serif;
+ font-size: 12px;
+ background-color: #B2B6C1;
+ margin: 0;
+ text-align: center;
+ color: #000;
+}
+
+a {
+ color: #2E5493;
+ text-decoration: none;
+}
+
+a:hover {
+ color: #001B48;
+}
+
+h1 {
+ font-size: 2em;
+ font-family: Droid Serif, Georgia, serif;
+ margin-bottom: .3em;
+}
+
+h2 {
+ font-size: 1em;
+}
+
+.pict {
+ border: 2px solid #FFFFFF;
+ -moz-box-shadow: 0 0 10px #000000;
+ -webkit-box-shadow: 0 0 10px #000000;
+ box-shadow: 0 0 10px #000000;
+}
+
+li {
+ margin: 0;
+ padding: 0;
+}
+
+.textshadow {
+ -webkit-text-shadow: 1px 2px 3px #000;
+ -moz-text-shadow: 1px 2px 3px #000;
+ text-shadow: 1px 2px 4px #000;
+}
+
+.borderradius {
+ -webkit-border-radius: 5px;
+ -moz-border-radius: 5px;
+ border-radius: 5px;
+}
+
+.borderradius_3 {
+ -moz-border-radius: 3px;
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+}
+
+.borderradius_10 {
+ -moz-border-radius: 10px;
+ -webkit-border-radius: 10px;
+ border-radius: 10px;
+}
+
+.boxshadow {
+ -moz-box-shadow: 0 0 6px #8A8B94;
+ -webkit-box-shadow: 0 0 6px #8A8B94;
+ box-shadow: 0 0 6px #8A8B94;
+}
+
+.icon {
+ position: absolute;
+ text-align: center;
+ left: 0;
+ top: 0;
+ width: 16px;
+}
+
+.icon i {
+ background-image: url('../img/sprite.png');
+ background-repeat: no-repeat;
+ display: inline-block;
+ height: 16px;
+ width: 16px;
+}
+
+.project_icon i {
+ background-position: 100% 100%;
+}
+
+.context_icon i {
+ background-position: -48px 100%;
+}
+
+p {
+ margin-top: 1em;
+ line-height: 1.4em;
+}
+
+#header, #page, #footer {
+ padding: 0 10px;
+}
+
+#wrapper {
+ text-align: left;
+}
+
+#header {
+}
+
+#header #focus_on {
+ -moz-border-radius: 0 0 5px 5px;
+ -webkit-border-radius: 0 0 10px 10px;
+ border-radius: 0 0 10px 10px;
+ background: none repeat scroll 0 0 #737373;
+ background: -moz-linear-gradient(top, #fff 0%, #DADADA 100%);
+ background: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#DADADA));
+ font-size:1em;
+ height:35px;
+ line-height:20px;
+ padding: 0 1em;
+ text-align:center;
+}
+
+#header #focus_on ul {
+ list-style:none outside none;
+}
+
+#header #focus_on li {
+ display:block;
+ position:relative;
+ float:left;
+ margin:0;
+ border-right: 1px solid #DADADA;
+}
+
+#header #focus_on li a,
+#header #focus_on li a:link,
+#header #focus_on li a:visited {
+ display:block;
+ color:#000;
+ height:35px;
+ line-height: 35px;
+ text-decoration:none;
+ padding: 0 10px 0 30px;
+}
+
+#header #focus_on a:hover,
+#header #focus_on a.active {
+ background:none repeat scroll 0 0 rgba(255,255,255, 0.5);
+}
+
+#header #focus_on li.title {
+ font-family: Arial;
+ padding-right: 1.5em;
+ margin-left: 1em;
+ line-height: 35px;
+ font-size: 1.4em;
+}
+
+#header #focus_on li a span.icon {
+ position:absolute;
+ text-align:center;
+ left:8px;
+ top:2px;
+ width:18px;
+}
+
+#header #focus_on li a span i {
+ background-image: url('../img/sprite.png');
+ background-repeat:no-repeat;
+ display: inline-block;
+ height: 16px;
+ width: 16px;
+}
+
+#header #focus_on a.all span i {
+ background-position: 0 -32px;
+}
+#header #focus_on a.thing span i {
+ background-position: 0 0;
+}
+#header #focus_on a.waiting span i {
+ background-position: -16px 0;
+}
+#header #focus_on a.next span i {
+ background-position: -32px 0;
+}
+#header #focus_on a.deferred span i {
+ background-position: -48px 0;
+}
+#header #focus_on a.delegated span i {
+ background-position: -64px 0;
+}
+#header #focus_on a.incubate span i {
+ background-position: -16px -16px;
+}
+#header #focus_on a.done span i {
+ background-position: 0 -16px;
+}
+
+#footer {
+ font-size: .9em;
+ font-style: italic;
+ padding-top: 10px;
+ color:#555;
+}
+
+/* --------------------- */
+
+#content {
+ margin-left: 215px;
+ padding: 0 10px;
+}
+
+/* --------------------- */
+
+#sidebar {
+ float: left;
+ margin-left: 10px;
+ background-color: #D2DBE5;
+ padding: 1em;
+ margin-top: 7px;
+ width: 180px;
+ border-radius: 3px;
+ -moz-border-radius: 3px;
+}
+
+#sidebar h2 {
+ position: relative;
+ padding: 1px 0 .5em 20px;
+ margin: .5em 0 0 0;
+ line-height: 16px;
+ border-bottom: 1px solid #B6BDC7;
+}
+
+#sidebar ul {
+ border-top: 1px solid #EAF4FF;
+ padding-bottom: 1em;
+ padding-top: .5em;
+}
+
+#sidebar ul li {
+ margin: 2px 0;
+ padding: 2px 0;
+}
+
+#sidebar ul li a {
+ padding: 0 .6em;
+ background-position: 0;
+ background-repeat: no-repeat;
+ line-height: 16px;
+}
+
+#sidebar ul li.active {
+ border-radius: 16px;
+ -moz-border-radius: 16px;
+ background-color: #375B8D;
+}
+
+#sidebar ul li.active a {
+ color: #FFF;
+}
+
+#sidebar ul li.active span.progress {
+ background-color: #fff;
+ color: #333;
+}
+
+#sidebar ul li span.progress {
+ border-radius: 10px;
+ -moz-border-radius: 10px;
+ background-color: #2E33C9;
+ width: 30px;
+ overflow: hidden;
+ color: #fff;
+ padding: 0 .6em;
+}
+
+#sidebar ul li span.done {
+ background-color: #149A12;
+}
+
+#sidebar ul li span.todo {
+ border-radius: 10px;
+ -moz-border-radius: 10px;
+ background-color: #FFF;
+ width: 30px;
+ overflow: hidden;
+ color: #333;
+ padding: 0 .6em;
+}
+/* --------------------- */
+
+#actions {
+ background: #333;
+ float: right;
+}
+
+#actions a {
+ display: block;
+ color: #fff;
+ padding: .3em .6em;
+}
+
+/* --------------------- */
+
+.thing-list li.state_1, .state_thing {}
+.thing-list li.state_1 .status_icons a {
+ background-position: 0 0;
+}
+
+.thing-list li.state_2, .state_waiting {}
+.thing-list li.state_2 .status_icons a {
+ background-position: -16px 0;
+}
+
+.thing-list li.state_3, .state_actionable {}
+.thing-list li.state_3 .status_icons a {
+ background-position: -32px 0;
+}
+
+.thing-list li.state_4, .state_delegated {}
+.thing-list li.state_4 .status_icons a {
+ background-position: 16px 0;
+}
+
+.thing-list li.state_5, .state_deferred { height: 22px; }
+.thing-list li.state_5 .status_icons a {
+ background-position: 32px 0;
+}
+
+.thing-list li.state_6, .state_incubate {}
+.thing-list li.state_6 .status_icons a {
+ background-position: -16px -16px;
+}
+
+.thing-list li.state_7, .state_done {
+ background: #D6D6E6 !important;
+ -moz-box-shadow: none;
+ color: #333;
+}
+
+.thing-list li.state_7 .status_icons a {
+ background-position: 0 -16px;
+}
+
+/* ------------------- */
+.thing-list li.progress-thing {
+ height: 22px;
+}
+
+.thing-list li.progress-thing .progress {
+ background: -moz-linear-gradient(top, #6F6F6F 0%, #C1C1C1 100%);;
+ margin-top: 10px;
+ height: 5px;
+}
+
+.thing-list li {
+ margin-top: .6em;
+ background:none repeat scroll 0 0 #FCFCFC;
+ background: -moz-linear-gradient(top, #FFF 0%, #EBEBEB 100%);
+ background: -webkit-gradient(linear, left top, left bottom, from(#FFF), to(#EBEBEB));
+ padding: 1em .8em;
+ height: 1em;
+}
+
+.thing-list li:hover {
+ background: -moz-linear-gradient(top, #FFFBD6 30%, #FFF490 70%);
+ background: -webkit-gradient(linear, left top, left bottom, from(#FFF), to(#EBEBEB));
+
+}
+
+.thing-list li a {
+ color: #000;
+}
+
+.thing-list li input {
+ margin: 0;
+ padding: 0;
+}
+
+.thing-list li .status_icons {
+ float: right;
+ margin-top: -2px;
+}
+
+.thing-list li .status_icons a {
+ display: block;
+ width: 16px; height: 16px;
+ background-image: url('../img/sprite.png');
+}
+
+.thing-list li .context {
+ float: right;
+ margin-right: .5em;
+}
+
+.thing-list li a.context_tag {
+ padding: 3px 4px;
+ background: #9AA1B5;
+ color: #FFFFFF;
+}
+
+.thing-list li a.context_tag:hover {
+ background: #5C7ACF;
+ color: #FFFFFF;
+}
+
+.thing-list li a.project_tag {
+ background-color: #C2DCFF;
+ color: #00395F;
+ padding: 3px 4px;
+ margin-right: .5em;
+}
+
+.thing-list li a.project_tag:hover {
+ background-color: #96C3FF;
+}
+
+.thing-list li.state_7 a.context_tag,
+.thing-list li.state_7 a.project_tag {
+ background-color: #D6D6E6;
+ color: #333;
+}
+
+.thing-list li.state_7 a.context_tag:hover,
+.thing-list li.state_7 a.project_tag:hover {
+ background: #9AA1B5;
+ color: #FFFFFF;
+}
+
+.thing-list li .duedate {
+ float: left;
+ width: 46px;
+ background-color: #FFFFFF;
+ text-align: center;
+ margin-top: -16px;
+ margin-left: -5px;
+ margin-right: .6em;
+}
+.thing-list li .duedate .month {
+ background-color: #AB0000;
+ color: #FFFFFF;
+ padding: 0 .5em;
+ -moz-border-radius: 3px 3px 0 0;
+ -webkit-border-radius: 3px 3px 0 0;
+ border-radius: 3px 3px 0 0;
+ line-height: 16px;
+}
+
+.thing-list li .duedate .day {
+ padding: 2px;
+ font-size: 18px;
+ font-family: serif;
+ font-weight: bold;
+ line-height: 26px;
+}
+
+.hidden {
+ height:1px;
+ left:-9999px;
+ overflow:hidden;
+ position:absolute;
+ top:0;
+ width:1px;
+}
+
+.pagination {
+ margin: 2em 0;
+ text-align: right;
+}
+
+.pagination a {
+ background-color: #fff;
+ padding: .5em;
+ font-weight: bold;
+}
+
+.pagination .current {
+ margin: 0 1em;
+}
BIN  static/img/favicon.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  static/img/sprite.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  static/img/sprite.xcf
Binary file not shown
0  templates/gtd/context_detail.html
No changes.
0  templates/gtd/context_list.html
No changes.
139 templates/gtd/dashboard.html
@@ -0,0 +1,139 @@
+{% extends 'layouts/base.html' %}
+{%block title %} Dashboard {% endblock %}
+{%block extrahead %}
+<script type="text/javascript">
+</script>
+{% endblock %}
+
+{% block header %}
+ <div id="focus_on" class="boxshadow">
+ <ul>
+ <li class="title">Focus on</li>
+ <li>
+ <a href="." class="all{% if not status %} active{% endif %}">
+ <span class="icon"><i> </i></span>
+ <span>All</span>
+ </a>
+ </li>
+ <li>
+ <a href="?status=1" class="thing{% if status == '1' %} active{% endif %}">
+ <span class="icon"><i> </i></span>
+ <span>Things</span>
+ </a>
+ </li>
+ <li>
+ <a href="?status=2" class="waiting{% if status == '2' %} active{% endif %}">
+ <span class="icon"><i></i></span>
+ <span>Waiting</span>
+ </a>
+ </li>
+ <li>
+ <a href="?status=3" class="next{% if status == '3' %} active{% endif %}">
+ <span class="icon"><i></i></span>
+ <span>Next actions</span>
+ </a>
+ </li>
+ <li>
+ <a href="?status=4" class="delegated{% if status == '4' %} active{% endif %}">
+ <span class="icon"><i></i></span>
+ <span>Delegated</span>
+ </a>
+ </li>
+ <li>
+ <a href="?status=5" class="deferred{% if status == '5' %} active{% endif %}">
+ <span class="icon"><i></i></span>
+ <span>Deferred</span>
+ </a>
+ </li>
+ <li>
+ <a href="?status=6" class="incubate{% if status == '6' %} active{% endif %}">
+ <span class="icon"><i></i></span>
+ <span>Someday</span>
+ </a>
+ </li>
+ <li>
+ <a href="?status=7" class="done{% if status == '7' %} active{% endif %}">
+ <span class="icon"><i></i></span>
+ <span>Finish</span>
+ </a>
+ </li>
+ </ul>
+ </div>
+{% endblock %}
+
+{% block sidebar %}
+ <h2><span class="context_icon icon"><i></i></span>Contexts</h2>
+ <ul id="contexts">
+ <li class="{% if not context_id %}active{% endif %}"><a href="?{{query_string}}context=">All</a></li>
+ {% for object in contexts %}
+ <li class="{% if context_id == object.id %}active{% endif %}">
+ <a href="?{{query_string}}context={{object.id}}">{{object}}</a>
+ {% if object.thing_todo_count > 0 %}
+ <span class="todo">{{object.thing_todo_count}}</span>
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+
+ <h2><span class="project_icon icon"><i></i></span>Projects</h2>
+ <ul id="projects">
+ <li class="{% if not project_id %}active{% endif %}"><a href="?{{query_string}}project=">All</a></li>
+ {% for object in projects %}
+ <li class="{% if project_id == object.id %}active{% endif %}">
+ <a href="?{{query_string}}project={{object.id}}">{{object}}</a>
+ <!-- {% if project.progress < 100 %} -->
+ <span class="progress">{{object.progress}}%</span>
+ {% else %}
+ <span class="progress done"> </span>
+ {% endif %}
+ </li>
+ {% endfor%}
+ </ul>
+{% endblock %}
+
+{% block content %}
+ {% if context %}
+ <div id="actions" class="borderradius_10 boxshadow">
+ <a href="{% url admin:gtd_thing_add %}?context={{context.id}}">Add thing</a>
+ </div>
+ <h1>{{context}}</h1>
+ <h2>{{context.name}}</h2>
+ <dl>
+ <dt>Description:</dt>
+ <dd>{{context.description}}</dd>
+ </dl>
+ {% endif %}
+ {% if project %}
+ <div id="actions" class="borderradius_10 boxshadow">
+ <a href="{% url admin:gtd_thing_add %}?project={{project.id}}">Add thing</a>
+ </div>
+ <h1>{{project}}</h1>
+ <h2>{{project.name}}</h2>
+ <dl>
+ <dt>Description:</dt>
+ <dd>{{project.description}}</dd>
+ </dl>
+ {% endif %}
+<ul class="thing-list">
+{% for thing in things.object_list %}
+ {% include 'gtd/thing_list_item.html'%}
+{% endfor %}
+</ul>
+
+<div class="pagination">
+ {% if things.has_previous %}
+ <a href="?{{query_string}}page=1" class="borderradius boxshadow">« start</a>
+ <a href="?{{query_string}}page={{ things.previous_page_number }}" class="borderradius boxshadow">previous</a>
+ {% endif %}
+
+ <span class="current">
+ Page {{ things.number }} of {{ things.paginator.num_pages }}
+ </span>
+
+ {% if things.has_next %}
+ <a href="?{{query_string}}page={{ things.next_page_number }}" class="borderradius boxshadow">next</a>
+ <a href="?{{query_string}}page={{ things.end_index }}" class="borderradius boxshadow">end »</a>
+ {% endif %}
+</div>
+
+{% endblock %}
0  templates/gtd/project_detail.html
No changes.
0  templates/gtd/project_list.html
No changes.
21 templates/gtd/status_icons.html
@@ -0,0 +1,21 @@
+{% if thing.status == thing.STATE_THING %}
+<a href="#" title="a Thing"> </a>
+{% endif %}
+{% if thing.status == thing.STATE_WAITING %}
+<a href="#" title="Waiting for"> </a>
+{% endif %}
+{% if thing.status == thing.STATE_DELEGATED %}
+<a href="#" title="Delegated"> </a>
+{% endif %}
+{% if thing.status == thing.STATE_DEFERRED %}
+<a href="#" title="Deferred"> </a>
+{% endif %}
+{% if thing.status == thing.STATE_ACTIONABLE %}
+<a href="#" title="Actionable"> </a>
+{% endif %}
+{% if thing.status == thing.STATE_DONE %}
+<a href="#" title="Done"> </a>
+{% endif %}
+{% if thing.status == thing.STATE_INCUBATE %}
+<a href="#" title="Some Day / Maybe"> </a>
+{% endif %}
0  templates/gtd/thing_detail.html
No changes.
0  templates/gtd/thing_list.html
No changes.
17 templates/gtd/thing_list_item.html
@@ -0,0 +1,17 @@
+<li class="borderradius_3 boxshadow state_{{thing.status}} deleted_{{thing.deleted|lower}}{% if thing.progress > 0 and not thing.status == thing.STATE_DONE %} progress-thing{% endif %}">
+<div class="status_icons">{% include 'gtd/status_icons.html' %}</div>
+<div class="context">
+ <a href="{% url admin:gtd_context_change thing.context.id %}" class="context_tag borderradius_10">{{thing.context}}</a>
+</div>
+<div class="thing-info">
+{% if thing.status == thing.STATE_DEFERRED %}
+<div class="duedate borderradius_3 boxshadow">
+ <div class="month">{{thing.schedule|date:"b"|default:"&nbsp;"}}</div>
+ <div class="day">{{thing.schedule.day|default:"?"}}</div>
+</div>
+{% endif %}
+<span>{% if thing.project %}<a class="project_tag borderradius_3" href="{% url admin:gtd_project_change thing.project.id %}">{{thing.project}}</a>{% endif %}
+<a href="{% url admin:gtd_thing_change thing.id %}">{{thing.name}}</a></span><br />
+</div>
+{% if thing.progress > 0 and not thing.status == thing.STATE_DONE %}<div class="progress borderradius_3" style="width: {{thing.progress}}%;"></div>{% endif %}
+</li>
19 templates/layouts/base.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="es" lang="es">
+<head>
+ <title>{%block title %}{% endblock %}</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+ <link rel="icon" href="{{MEDIA_URL}}img/favicon.png" type="image/png" />
+ <link rel="stylesheet" href="{{MEDIA_URL}}css/styles.css" type="text/css" media="screen" />
+ {% block extrahead %}{% endblock %}
+</head>
+
+<body>
+<div id="wrapper">
+ <div id="header">{% block header %}{% endblock %}</div>
+ <div id="sidebar" class="boxshadow">{% block sidebar %}{% endblock %}</div>
+ <div id="content">{% block content %}{% endblock %}</div>
+ <div id="footer"></div>
+</div>
+</body>
+</html>
21 templates/registration/login.html
@@ -0,0 +1,21 @@
+{% extends "databrowse/base.html" %}
+{% block databrowse_title %}Login{% endblock %}
+
+{% block content %}
+{% if form.errors %}
+<p>Por favor, introduzca un nombre de usuario y contraseña correctos.
+Note que ambos campos son sensibles a mayúsculas/minúsculas..</p>
+{% endif %}
+<form method="post" action="{% url django.contrib.auth.views.login %}">{% csrf_token %}
+ <p>
+ {{ form.username.label_tag }}<br />
+ {{ form.username }}
+ </p>
+ <p>
+ {{ form.password.label_tag }}<br />
+ {{ form.password }}
+ </p>
+ <input type="submit" value="Inicio de sesión" />
+ <input type="hidden" name="next" value="{{ next }}" />
+</form>
+{% endblock %}
26 urls.py
@@ -0,0 +1,26 @@
+from django.conf.urls.defaults import *
+from django.contrib import admin
+from django.contrib import databrowse
+from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect
+
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+ (r'^admin/', include(admin.site.urls)),
+ (r'^databrowse/(.*)', login_required(databrowse.site.root)),
+ (r'^accounts/login/$', 'django.contrib.auth.views.login'),
+ (r'^gtd/', include('gtd.urls')),
+ url(r'^$', lambda request: redirect('/gtd')),
+)
+
+if settings.DEBUG:
+ from os.path import join, dirname, abspath
+ urlpatterns += patterns('',
+ url(r'^media/admin/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': abspath(join(dirname(admin.__file__), 'media')), 'show_indexes': True}),
+ url(r'^static/(?P<path>.*)$', 'django.views.static.serve',
+ {'document_root': settings.MEDIA_ROOT, 'show_indexes': True}),
+)
8 wsgi.py
@@ -0,0 +1,8 @@
+import os
+import sys
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
+
+import django.core.handlers.wsgi as w
+application = w.WSGIHandler()
+
Please sign in to comment.
Something went wrong with that request. Please try again.