Skip to content
This repository has been archived by the owner on Aug 20, 2018. It is now read-only.

Commit

Permalink
#Bug 1014435 - [admin] Show the duration (time taken) by the user fo…
Browse files Browse the repository at this point in the history
…r a task to complete

Bug 1014435 - [admin] Show the duration (time taken) by the user for task to complete
  • Loading branch information
tesssie committed Jan 24, 2015
1 parent e0be690 commit 63ccb51
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 7 deletions.
5 changes: 4 additions & 1 deletion oneanddone/base/static/css/one-and-done.less
Expand Up @@ -167,7 +167,7 @@ footer {
}
.feedback-form {
width: 95%;
text-align: right;
text-align: left;
label:first-child {
.visually-hidden;
}
Expand All @@ -177,6 +177,9 @@ footer {
.no-feedback {
margin-right: 1rem;
}
.right {
text-align: right;
}
}
.home #colophon {
margin-top: 4em;
Expand Down
2 changes: 1 addition & 1 deletion oneanddone/tasks/admin.py
Expand Up @@ -14,7 +14,7 @@ def save_model(self, request, obj, form, change):


class FeedbackAdmin(admin.ModelAdmin):
list_display = ('task', 'user', 'state', 'created')
list_display = ('task', 'user', 'state', 'created', 'time_spent_in_minutes')
search_fields = ('text',)
readonly_fields = ('task', 'user', 'state', 'text')
exclude = ('attempt',)
Expand Down
5 changes: 4 additions & 1 deletion oneanddone/tasks/forms.py
Expand Up @@ -25,9 +25,12 @@ def __init__(self, *args, **kwargs):


class FeedbackForm(forms.ModelForm):
time_spent_in_minutes = forms.IntegerField(
label=_(' How many minutes did you spend on the task?'))

class Meta:
model = Feedback
fields = ('text',)
fields = ('text', 'time_spent_in_minutes')


class PreviewConfirmationForm(forms.Form):
Expand Down
@@ -0,0 +1,183 @@
# -*- coding: 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 'Feedback.time_spent_in_minutes'
db.add_column('tasks_feedback', 'time_spent_in_minutes',
self.gf('django.db.models.fields.IntegerField')(null=True, blank=True),
keep_default=False)


def backwards(self, orm):
# Deleting field 'Feedback.time_spent_in_minutes'
db.delete_column('tasks_feedback', 'time_spent_in_minutes')


models = {
'auth.group': {
'Meta': {'object_name': 'Group'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
'auth.permission': {
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
'auth.user': {
'Meta': {'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
},
'contenttypes.contenttype': {
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
},
'tasks.bugzillabug': {
'Meta': {'object_name': 'BugzillaBug'},
'bugzilla_id': ('django.db.models.fields.IntegerField', [], {'unique': 'True', 'max_length': '20'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'summary': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'tasks.feedback': {
'Meta': {'object_name': 'Feedback'},
'attempt': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['tasks.TaskAttempt']", 'unique': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'text': ('django.db.models.fields.TextField', [], {}),
'time_spent_in_minutes': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
},
'tasks.task': {
'Meta': {'ordering': "['priority', 'difficulty']", 'object_name': 'Task'},
'batch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['tasks.TaskImportBatch']", 'null': 'True', 'blank': 'True'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'difficulty': ('django.db.models.fields.IntegerField', [], {'default': '1'}),
'end_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'execution_time': ('django.db.models.fields.IntegerField', [], {'default': '30'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'instructions': ('django.db.models.fields.TextField', [], {}),
'is_draft': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'is_invalid': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'next_task': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'previous_task'", 'null': 'True', 'to': "orm['tasks.Task']"}),
'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owner'", 'to': "orm['auth.User']"}),
'prerequisites': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'priority': ('django.db.models.fields.IntegerField', [], {'default': '3'}),
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['tasks.TaskProject']", 'null': 'True', 'blank': 'True'}),
'repeatable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'short_description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
'team': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['tasks.TaskTeam']"}),
'type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['tasks.TaskType']", 'null': 'True', 'blank': 'True'}),
'why_this_matters': ('django.db.models.fields.TextField', [], {'blank': 'True'})
},
'tasks.taskattempt': {
'Meta': {'ordering': "['-modified']", 'object_name': 'TaskAttempt'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'requires_notification': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'state': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taskattempt_set'", 'to': "orm['tasks.Task']"}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'})
},
'tasks.taskimportbatch': {
'Meta': {'object_name': 'TaskImportBatch'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'query': ('django.db.models.fields.TextField', [], {}),
'source': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'tasks.taskinvalidationcriterion': {
'Meta': {'object_name': 'TaskInvalidationCriterion'},
'batches': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['tasks.TaskImportBatch']", 'symmetrical': 'False'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'}),
'field_name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'field_value': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'relation': ('django.db.models.fields.IntegerField', [], {'default': '0'})
},
'tasks.taskkeyword': {
'Meta': {'object_name': 'TaskKeyword'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', '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': '255'}),
'task': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'keyword_set'", 'to': "orm['tasks.Task']"})
},
'tasks.taskmetrics': {
'Meta': {'ordering': "['-completed_users']", 'object_name': 'TaskMetrics'},
'abandoned_users': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'closed_users': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'completed_users': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'incomplete_users': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
'task': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['tasks.Task']", 'unique': 'True'}),
'too_short_completed_attempts_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'user_completes_then_completes_another_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'user_completes_then_takes_another_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
'user_takes_then_quits_count': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
},
'tasks.taskproject': {
'Meta': {'object_name': 'TaskProject'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', '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': '255'})
},
'tasks.taskteam': {
'Meta': {'object_name': 'TaskTeam'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', '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': '255'})
},
'tasks.tasktype': {
'Meta': {'object_name': 'TaskType'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'creator': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', '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': '255'})
}
}

complete_apps = ['tasks']
11 changes: 10 additions & 1 deletion oneanddone/tasks/models.py
Expand Up @@ -12,6 +12,7 @@
from django.db import models
from django.db.models import Avg, Count, F, Q
from django.utils import timezone
from django.core.validators import MaxValueValidator, MinValueValidator

import bleach
import jinja2
Expand All @@ -34,6 +35,8 @@ def __unicode__(self):
class Feedback(CachedModel, CreatedModifiedModel):
attempt = models.OneToOneField('TaskAttempt')
text = models.TextField()
time_spent_in_minutes = models.IntegerField(
validators=[MinValueValidator(0), MaxValueValidator(99999)], blank=True, null=True)

def __unicode__(self):
return u'Feedback: {user} for {task}'.format(
Expand Down Expand Up @@ -69,6 +72,12 @@ def attempt_length_in_minutes(self):
end_seconds = time.mktime(self.modified.timetuple())
return round((end_seconds - start_seconds) / 60, 1)

@property
def time_spent_in_minutes(self):
if self.has_feedback:
return self.feedback.time_spent_in_minutes
return None

@property
def feedback_display(self):
if self.has_feedback:
Expand Down Expand Up @@ -501,7 +510,7 @@ def why_this_matters_html(self):
def users_who_completed_this_task(self):
return User.objects.filter(
taskattempt__in=TaskAttempt.objects.filter(
task=self.id, state=TaskAttempt.FINISHED)).distinct()
task=self.id, state=TaskAttempt.FINISHED)).distinct()

def _yield_html(self, field):
"""
Expand Down
6 changes: 6 additions & 0 deletions oneanddone/tasks/templates/tasks/activity_listing.html
Expand Up @@ -24,6 +24,12 @@
<div>{{ _("Team: {team}")|fe(team=attempt.task.team.name) }}</div>
<div>{{ _('Date {state}: {modified}')|fe(state=attempt.get_state_display(),
modified=attempt.modified.strftime('%Y-%m-%d: %I:%M:%S %p').lower()) }}</div>
<div>
{% if attempt.has_feedback %}
{{_("Time spent in minutes: {time}")|fe(time=attempt.time_spent_in_minutes) }}m
{% endif %}
</div>
</div>
<div class="feedback{% if not attempt.has_feedback %} no-feedback{% endif %}">
{{ attempt.feedback_display }}
</div>
Expand Down
5 changes: 5 additions & 0 deletions oneanddone/tasks/templates/tasks/emails/feedback_email.txt
Expand Up @@ -5,3 +5,8 @@ The task: {{ task_name }} ({{ task_link }}) was {{ task_state }}.
Feedback:

{{ feedback }}

Time spent on the task:

{{ time_spent_on_task }}m

16 changes: 13 additions & 3 deletions oneanddone/tasks/templates/tasks/feedback.html
Expand Up @@ -33,9 +33,19 @@ <h4>{{ _('Thank you!') }}</h4>

<form method="post" class="feedback-form">
{{ csrf() }}
{{ form.as_p() }}
<a href="{{ url('tasks.whats_next', attempt.task.id) }}" class="no-feedback">{{ _('No, thanks.') }}</a>
<button type="submit" class="button">{{ _('Submit feedback') }}</button>
<p>
{{ form.text }}
{{ form.text.errors }}
</p>
<p>
{{ form.time_spent_in_minutes.label }}
{{ form.time_spent_in_minutes}}
{{ form.time_spent_in_minutes.errors }}
</p>
<div class="right">
<a href="{{ url('tasks.whats_next', attempt.task.id) }}" class="no-feedback">{{ _('No, thanks.') }}</a>
<button type="submit" class="button">{{ _('Submit feedback') }}</button>
</div>
</form>
</main>
{% endblock %}
1 change: 1 addition & 0 deletions oneanddone/tasks/views.py
Expand Up @@ -106,6 +106,7 @@ def form_valid(self, form):
'task_name': task_name,
'task_link': task_link,
'task_state': feedback.attempt.get_state_display(),
'time_spent_on_task': feedback.time_spent_in_minutes,
'feedback': feedback.text})

# Manually replace quotes and double-quotes as these get
Expand Down

0 comments on commit 63ccb51

Please sign in to comment.