diff options
-rw-r--r-- | snakeplan/projects/admin.py | 9 | ||||
-rw-r--r-- | snakeplan/projects/forms.py | 9 | ||||
-rw-r--r-- | snakeplan/projects/models.py | 107 | ||||
-rw-r--r-- | snakeplan/projects/urls.py | 24 | ||||
-rw-r--r-- | snakeplan/projects/views/stories.py | 29 |
5 files changed, 109 insertions, 69 deletions
diff --git a/snakeplan/projects/admin.py b/snakeplan/projects/admin.py index ba56193..e31187b 100644 --- a/snakeplan/projects/admin.py +++ b/snakeplan/projects/admin.py | |||
@@ -28,9 +28,10 @@ class ProjectAdmin(ModelAdmin): | |||
28 | ordering = ('name', ) | 28 | ordering = ('name', ) |
29 | 29 | ||
30 | 30 | ||
31 | site.register(models.Task) | ||
32 | site.register(models.Story) | ||
33 | site.register(models.Project, ProjectAdmin) | 31 | site.register(models.Project, ProjectAdmin) |
34 | site.register(models.Iteration) | 32 | site.register(models.Iteration) |
35 | site.register(models.DevelopmentIteration) | 33 | site.register(models.Story) |
36 | site.register(models.LoggedTime) | 34 | site.register(models.Release) |
35 | site.register(models.Feature) | ||
36 | site.register(models.Task) | ||
37 | site.register(models.Comment) | ||
diff --git a/snakeplan/projects/forms.py b/snakeplan/projects/forms.py new file mode 100644 index 0000000..68fbfe9 --- /dev/null +++ b/snakeplan/projects/forms.py | |||
@@ -0,0 +1,9 @@ | |||
1 | import models | ||
2 | from django.forms import ModelForm | ||
3 | |||
4 | |||
5 | class StoryForm(ModelForm): | ||
6 | |||
7 | class Meta: | ||
8 | model = models.Story | ||
9 | fields = ('project', 'name') | ||
diff --git a/snakeplan/projects/models.py b/snakeplan/projects/models.py index e707bb2..aca8e36 100644 --- a/snakeplan/projects/models.py +++ b/snakeplan/projects/models.py | |||
@@ -23,29 +23,23 @@ from django.db.models import Model | |||
23 | from django.contrib.auth.models import User | 23 | from django.contrib.auth.models import User |
24 | 24 | ||
25 | 25 | ||
26 | STATUSES = ( | 26 | DAY = ( |
27 | (0, 'Draft'), | 27 | (0, 'Sunday'), |
28 | (1, 'Defined'), | 28 | (1, 'Monday'), |
29 | (2, 'Estimated'), | 29 | (2, 'Tuesday'), |
30 | (3, 'Planned'), | 30 | (3, 'Wednesday'), |
31 | (4, 'Implemented'), | 31 | (4, 'Thursday'), |
32 | (5, 'Verified'), | 32 | (5, 'Friday'), |
33 | (6, 'Accepted'), | 33 | (6, 'Saturday'), |
34 | ) | ||
35 | |||
36 | DISPOSITIONS = ( | ||
37 | (0, 'Planned'), | ||
38 | (1, 'Carried Over'), | ||
39 | (2, 'Added'), | ||
40 | (3, 'Discovered'), | ||
41 | ) | 34 | ) |
42 | 35 | ||
43 | TASK_TYPES = ( | 36 | STATUSES = ( |
44 | (0, 'Feature'), | 37 | (0, 'Draft'), |
45 | (1, 'Debt'), | 38 | (1, 'Started'), |
46 | (2, 'Functional Test'), | 39 | (2, 'Finished'), |
47 | (3, 'Acceptance Test'), | 40 | (3, 'Delivered'), |
48 | (4, 'Overhead'), | 41 | (4, 'Accepted'), |
42 | (5, 'Rejected'), | ||
49 | ) | 43 | ) |
50 | 44 | ||
51 | 45 | ||
@@ -54,7 +48,14 @@ class Project(Model): | |||
54 | name = m.CharField(max_length=200) | 48 | name = m.CharField(max_length=200) |
55 | description = m.TextField(blank=True, null=True) | 49 | description = m.TextField(blank=True, null=True) |
56 | active = m.BooleanField(default=True) | 50 | active = m.BooleanField(default=True) |
57 | wiki_link = m.URLField(blank=True, null=True) | 51 | start_date = m.DateField() |
52 | |||
53 | iteration_starts = m.IntegerField(choices=DAY, default=0) | ||
54 | iteration_length = m.IntegerField(default=2) | ||
55 | initial_velocity = m.IntegerField(default=10) | ||
56 | velocity_time_period = m.IntegerField(default=3) | ||
57 | |||
58 | current_velocity = m.IntegerField(default=10) | ||
58 | 59 | ||
59 | def __unicode__(self): | 60 | def __unicode__(self): |
60 | return self.name | 61 | return self.name |
@@ -64,73 +65,71 @@ class Iteration(Model): | |||
64 | 65 | ||
65 | project = m.ForeignKey(Project) | 66 | project = m.ForeignKey(Project) |
66 | 67 | ||
67 | name = m.CharField(max_length=200) | 68 | name = m.CharField(max_length=200, blank=True, null=True) |
68 | description = m.TextField(blank=True, null=True) | 69 | description = m.TextField(blank=True, null=True) |
70 | start_date = m.DateField(blank=True, null=True) | ||
71 | end_date = m.DateField(blank=True, null=True) | ||
69 | 72 | ||
70 | # It should not be possible to delete the backlog | 73 | team_strength = m.DecimalField(decimal_places=2, max_digits=1, default=1) |
71 | can_delete = m.BooleanField(default=True) | ||
72 | 74 | ||
73 | def __unicode__(self): | 75 | def __unicode__(self): |
74 | return self.name | 76 | return self.name |
75 | 77 | ||
76 | 78 | ||
77 | # Not all iterations are actually open for development | ||
78 | # for example, the Backlog is not a development iteration. | ||
79 | class DevelopmentIteration(Iteration): | ||
80 | |||
81 | active = m.BooleanField(default=True) | ||
82 | start_date = m.DateField(blank=True, null=True) | ||
83 | end_date = m.DateField(blank=True, null=True) | ||
84 | |||
85 | |||
86 | class Story(Model): | 79 | class Story(Model): |
87 | 80 | ||
88 | class Meta: | 81 | class Meta: |
89 | verbose_name_plural = 'Stories' | 82 | verbose_name_plural = 'stories' |
90 | 83 | ||
91 | iteration = m.ForeignKey(Iteration) | 84 | project = m.ForeignKey(Project, related_name='stories') |
85 | iteration = m.ForeignKey(Iteration, blank=True, null=True) | ||
92 | tracker = m.ForeignKey(User, blank=True, null=True) | 86 | tracker = m.ForeignKey(User, blank=True, null=True) |
93 | customer = m.ForeignKey(User, blank=True, null=True, | 87 | customer = m.ForeignKey(User, blank=True, null=True, |
94 | related_name='story_customer') | 88 | related_name='story_customer') |
95 | 89 | ||
96 | name = m.CharField(max_length=200) | 90 | name = m.CharField(max_length=200) |
97 | disposition = m.IntegerField(choices=DISPOSITIONS, default=0) | ||
98 | status = m.IntegerField(choices=STATUSES, default=0) | 91 | status = m.IntegerField(choices=STATUSES, default=0) |
99 | priority = m.IntegerField() | 92 | accept_date = m.DateField(blank=True, null=True) |
100 | order = m.IntegerField() | ||
101 | description = m.TextField(blank=True, null=True) | 93 | description = m.TextField(blank=True, null=True) |
94 | order = m.IntegerField(default=0) | ||
102 | 95 | ||
103 | def __unicode__(self): | 96 | def __unicode__(self): |
104 | return self.name | 97 | return self.name |
105 | 98 | ||
106 | 99 | ||
100 | class Release(Story): | ||
101 | |||
102 | release_date = m.DateField() | ||
103 | |||
104 | |||
105 | class Feature(Story): | ||
106 | |||
107 | points = m.IntegerField(blank=True, null=True) | ||
108 | |||
109 | |||
110 | class Bug(Story): | ||
111 | pass | ||
112 | |||
113 | |||
107 | class Task(Model): | 114 | class Task(Model): |
108 | 115 | ||
109 | story = m.ForeignKey(Story) | 116 | story = m.ForeignKey(Story) |
110 | acceptor = m.ForeignKey(User, blank=True, null=True) | ||
111 | 117 | ||
112 | name = m.CharField(max_length=200) | 118 | name = m.CharField(max_length=200) |
113 | completed = m.BooleanField(default=False) | 119 | completed = m.BooleanField(default=False) |
114 | task_type = m.IntegerField(choices=TASK_TYPES, default=0) | 120 | order = m.IntegerField(default=0) |
115 | disposition = m.IntegerField(choices=DISPOSITIONS, default=0) | ||
116 | estimated_hours = m.DecimalField(decimal_places=2, max_digits=5) | ||
117 | description = m.TextField(blank=True, null=True) | ||
118 | 121 | ||
119 | def __unicode__(self): | 122 | def __unicode__(self): |
120 | return self.name | 123 | return self.name |
121 | 124 | ||
122 | 125 | ||
123 | class LoggedTime(Model): | 126 | class Comment(Model): |
124 | 127 | ||
125 | task = m.ForeignKey(Task) | 128 | user = m.ForeignKey(User) |
126 | person1 = m.ForeignKey(User, blank=True, null=True, related_name="person1") | 129 | story = m.ForeignKey(Story) |
127 | person2 = m.ForeignKey(User, blank=True, null=True, related_name="person2") | ||
128 | 130 | ||
129 | logged_date = m.DateField() | 131 | post_date = m.DateTimeField(auto_now=True) |
130 | start_time = m.DateTimeField(blank=True, null=True) | 132 | comment = m.TextField() |
131 | end_time = m.DateTimeField(blank=True, null=True) | ||
132 | duration = m.DecimalField(decimal_places=2, max_digits=5) | ||
133 | description = m.TextField(blank=True, null=True) | ||
134 | 133 | ||
135 | def __unicode__(self): | 134 | def __unicode__(self): |
136 | return self.description | 135 | return self.comment |
diff --git a/snakeplan/projects/urls.py b/snakeplan/projects/urls.py index c59024c..3716885 100644 --- a/snakeplan/projects/urls.py +++ b/snakeplan/projects/urls.py | |||
@@ -22,16 +22,18 @@ from django.conf.urls.defaults import patterns, url | |||
22 | 22 | ||
23 | 23 | ||
24 | urlpatterns = patterns('projects.views', | 24 | urlpatterns = patterns('projects.views', |
25 | url(r'^$', 'project.index', name='project-list'), | 25 | # url(r'^$', 'project.index', name='project-list'), |
26 | 26 | # | |
27 | # Projects | 27 | # # Projects |
28 | url(r'^create/', 'project.create_project', name='create-project'), | 28 | # url(r'^create/', 'project.create_project', name='create-project'), |
29 | url(r'^(\d+)/edit/', 'project.update_project', name='edit-project'), | 29 | # url(r'^(\d+)/edit/', 'project.update_project', name='edit-project'), |
30 | url(r'^(\d+)/', 'project.project_iterations', name='project-iterations'), | 30 | # url(r'^(\d+)/', 'project.project_iterations', name='project-iterations'), |
31 | 31 | # | |
32 | # Iterations | 32 | # # Iterations |
33 | url(r'^(\d+)/iterations/(\d+)/', 'project.index'), | 33 | # url(r'^(\d+)/iterations/(\d+)/', 'project.index'), |
34 | # | ||
35 | # # Stories | ||
36 | # url(r'^(\d+)/stories/(\d+)/', 'project.index', name='iteration-stories'), | ||
34 | 37 | ||
35 | # Stories | 38 | url(r'^story/create/', 'stories.create_story', name='create-story'), |
36 | url(r'^(\d+)/stories/(\d+)/', 'project.index', name='iteration-stories'), | ||
37 | ) | 39 | ) |
diff --git a/snakeplan/projects/views/stories.py b/snakeplan/projects/views/stories.py new file mode 100644 index 0000000..afcb373 --- /dev/null +++ b/snakeplan/projects/views/stories.py | |||
@@ -0,0 +1,29 @@ | |||
1 | # vim: set filencoding=utf8 | ||
2 | """ | ||
3 | Story Views | ||
4 | |||
5 | @author: Mike Crute (mcrute@gmail.com) | ||
6 | @organization: SoftGroup Interactive, Inc. | ||
7 | @date: July 10, 2010 | ||
8 | """ | ||
9 | |||
10 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
11 | # you may not use this file except in compliance with the License. | ||
12 | # You may obtain a copy of the License at | ||
13 | # | ||
14 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
15 | # | ||
16 | # Unless required by applicable law or agreed to in writing, software | ||
17 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
18 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
19 | # See the License for the specific language governing permissions and | ||
20 | # limitations under the License. | ||
21 | |||
22 | from django.views.generic import list_detail, create_update | ||
23 | from django.core.urlresolvers import reverse | ||
24 | |||
25 | from projects import models, forms | ||
26 | |||
27 | def create_story(request): | ||
28 | return create_update.create_object(request, form_class=forms.StoryForm, | ||
29 | post_save_redirect=reverse('create-story')) | ||