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

Commit

Permalink
Address second set of review comments
Browse files Browse the repository at this point in the history
  • Loading branch information
mjzffr committed Sep 28, 2014
1 parent 477dc54 commit 0d492af
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 188 deletions.
9 changes: 6 additions & 3 deletions oneanddone/tasks/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,17 @@ def state(self, feedback):
return feedback.attempt.get_state_display()


class BugzillaFieldAdmin(admin.ModelAdmin):
list_display = ('name', 'creator', 'modified')
class TaskInvalidationCriterionAdmin(RecordCreatorMixin, admin.ModelAdmin):
list_display = ('field_name', 'relation', 'field_value',
'creator', 'modified')
readonly_fields = ('creator', 'modified')
exclude = ('batches',)


admin.site.register(models.TaskTeam, TaskTeamAdmin)
admin.site.register(models.TaskProject, TaskProjectAdmin)
admin.site.register(models.TaskType, TaskTeamAdmin)
admin.site.register(models.TaskAttempt, TaskAttemptAdmin)
admin.site.register(models.Feedback, FeedbackAdmin)
admin.site.register(models.BugzillaField, BugzillaFieldAdmin)
admin.site.register(models.TaskInvalidationCriterion,
TaskInvalidationCriterionAdmin)
105 changes: 53 additions & 52 deletions oneanddone/tasks/bugzilla_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,56 @@

import requests

_baseurl = 'https://bugzilla.mozilla.org/rest/bug'


def _request_json(url, params):
""" Returns the json-encoded response from Bugzilla@Mozilla, if any """
headers = {'content-type': 'application/json', 'accept': 'application/json'}
r = requests.get(url, headers=headers, params=params)
data = r.json()
if not r.ok:
r.raise_for_status()
elif data.get('error'):
# According to
# http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Server/REST.html#ERRORS
errno = data.get('code')
message = ''.join([data.get('message'), ' (Error ', str(errno), ')'])
if errno >= 0 and errno <= 100000:
# Transient error (e.g. bad query)
raise ValueError(message)
if errno < 0 or errno > 100000:
# Fatal error in Bugzilla, or error thrown by the JSON-RPC
# library that Bugzilla uses
raise RuntimeError(message)
return data


def request_bugs(request_params, fields=['id', 'summary'], offset=0, limit=99):
""" Returns list of at most first `limit` bugs (starting at `offset`) from
Bugzilla@Mozilla, if any. The bugs are ordered by bug id.
"""
params = dict(request_params)
params.update({'include_fields': ','.join(fields),
'offset': offset, 'limit': limit})
return _request_json(_baseurl, params).get('bugs', [])


def request_bugcount(request_params):
params = dict(request_params)
params.update({'count_only': 1})
response = _request_json(_baseurl, params)
bug_count = response.get('bug_count', '0')
return int(bug_count)


def request_bug(bug_id, fields=['id', 'summary']):
""" Returns bug with id `bug_id` from Buzgilla@Mozilla, if any """
params = {'include_fields': ','.join(fields)}
url = ''.join([_baseurl, '/', str(bug_id)])
bugs = _request_json(url, params).get('bugs')
if bugs:
return bugs[0]
else:
return None

class BugzillaUtils(object):

def __init__(self):
self.baseurl = 'https://bugzilla.mozilla.org/rest/bug'

def _request_json(self, url, params):
""" Returns the json-encoded response from Bugzilla@Mozilla, if any """
headers = {'content-type': 'application/json', 'accept': 'application/json'}
r = requests.get(url, headers=headers, params=params)
data = r.json()
if not r.ok:
r.raise_for_status()
elif data.get('error'):
# According to
# http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService/Server/REST.html#ERRORS
errno = data.get('code')
message = ''.join([data.get('message'), ' (Error ', str(errno), ')'])
if errno >= 0 and errno <= 100000:
# Transient error (e.g. bad query)
raise ValueError(message)
if errno < 0 or errno > 100000:
# Fatal error in Bugzilla, or error thrown by the JSON-RPC
# library that Bugzilla uses
raise RuntimeError(message)
return data

def request_bugs(self, request_params, fields=['id', 'summary'],
offset=0, limit=99):
""" Returns list of at most first `limit` bugs (starting at `offset`) from
Bugzilla@Mozilla, if any. The bugs are ordered by bug id.
"""
params = dict(request_params)
params.update({'include_fields': ','.join(fields),
'offset': offset, 'limit': limit})
return self._request_json(self.baseurl, params).get('bugs', [])

def request_bugcount(self, request_params):
params = dict(request_params)
params.update({'count_only': 1})
response = self._request_json(self.baseurl, params)
bug_count = response.get('bug_count', '0')
return int(bug_count)

def request_bug(self, bug_id, fields=['id', 'summary']):
""" Returns bug with id `bug_id` from Buzgilla@Mozilla, if any """
params = {'include_fields': ','.join(fields)}
url = ''.join([self.baseurl, '/', str(bug_id)])
bugs = self._request_json(url, params).get('bugs')
if bugs:
return bugs[0]
else:
return None
72 changes: 37 additions & 35 deletions oneanddone/tasks/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
from urlparse import urlparse, parse_qs

from oneanddone.base.widgets import CalendarInput, HorizRadioSelect
from oneanddone.tasks.bugzilla_utils import request_bugcount, request_bugs
from oneanddone.tasks.models import BugzillaBug, Feedback, Task, TaskImportBatch, TaskInvalidationCriterion
from oneanddone.tasks.bugzilla_utils import BugzillaUtils
from oneanddone.tasks.models import (BugzillaBug, Feedback, Task,
TaskImportBatch,
TaskInvalidationCriterion)


class FeedbackForm(forms.ModelForm):
Expand All @@ -19,32 +21,24 @@ class Meta:
fields = ('text',)


class TaskInvalidationCriterionForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TaskInvalidationCriterionForm, self).__init__(*args, **kwargs)
class TaskInvalidationCriterionForm(forms.Form):
criterion = forms.ModelChoiceField(queryset=
TaskInvalidationCriterion.objects.all())

class Meta:
model = TaskInvalidationCriterion
fields = ('field_name', 'relation', 'field_value')
widgets = {
'field_value': forms.TextInput(attrs={'size': 15})
}

class BaseTaskInvalidCriteriaFormSet(forms.models.BaseModelFormSet):
class BaseTaskInvalidCriteriaFormSet(forms.formsets.BaseFormSet):
# make it impossible to submit an empty form as part of the formset
def __init__(self, *args, **kwargs):
super(BaseTaskInvalidCriteriaFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False


TaskInvalidCriteriaFormSet = forms.models.modelformset_factory(
TaskInvalidationCriterion,
form=TaskInvalidationCriterionForm,
TaskInvalidCriteriaFormSet = forms.formsets.formset_factory(
TaskInvalidationCriterionForm,
formset=BaseTaskInvalidCriteriaFormSet)



class TaskImportBatchForm(forms.ModelForm):
def save(self, creator, *args, **kwargs):
self.instance.creator = creator
Expand All @@ -59,29 +53,33 @@ def clean(self):
query = parse_qs(urlparse(query_url).query)
if not query:
raise forms.ValidationError(_('For the query URL, please provide '
'a full URL that includes search '
'parameters.'))
'a full URL that includes search '
'parameters.'))

try:
bugcount = request_bugcount(query)
bugcount = BugzillaUtils().request_bugcount(query)
except ValueError as e:
raise forms.ValidationError(_(' '.join(['External error:', str(e)])))
except (RuntimeError, RequestException):
raise forms.ValidationError(_('Sorry. we cannot retrieve any '
'data from Bugzilla at this time. Please '
'report this to the One and Done team.'))
'data from Bugzilla at this time. '
'Please report this to the One and '
'Done team.'))

if not bugcount:
raise forms.ValidationError(_('Your query does not return any results.'))
elif bugcount > max_results:
raise forms.ValidationError(_('Your query returns more than {num} items.').format(num=str(max_results)))
message = _('Your query returns more than '
'{num} items.').format(num=str(max_results))
raise forms.ValidationError(message)

fresh_bugs = self._get_fresh_bugs(query_url, query, bugcount, max_batch_size)

if not fresh_bugs:
raise forms.ValidationError(_('The results of this query have '
'all been imported before. To import these results again '
'as different tasks, please use a different query.'))
message = _('The results of this query have all been imported '
'before. To import these results again as different '
'tasks, please use a different query.')
raise forms.ValidationError(message)

cleaned_data['_fresh_bugs'] = fresh_bugs

Expand All @@ -92,10 +90,12 @@ def _get_fresh_bugs(query_url, query, max_results, max_batch_size):
''' Returns at most first `max_batch_size` bugs (ordered by bug id)
that have not already been imported via `query`.
'''
existing_bug_ids = BugzillaBug.objects.filter(tasks__batch__query__exact=query_url).values_list('bugzilla_id', flat=True)
existing_bug_ids = BugzillaBug.objects.filter(
tasks__batch__query__exact=query_url).values_list('bugzilla_id',
flat=True)

def fetch(query, limit, offset):
new_bugs = request_bugs(query, limit=limit, offset=offset)
new_bugs = BugzillaUtils().request_bugs(query, limit=limit, offset=offset)
return [bug for bug in new_bugs if bug['id'] not in existing_bug_ids]

fresh_bugs = []
Expand All @@ -115,11 +115,10 @@ class Meta:


class TaskForm(forms.ModelForm):
keywords = forms.CharField(help_text=_('Please use commas to separate '
'your keywords.'),
required=False,
widget=forms.TextInput(
attrs={'class': 'medium-field'}))
keywords = (forms.CharField(
help_text=_('Please use commas to separate your keywords.'),
required=False,
widget=forms.TextInput(attrs={'class': 'medium-field'})))

def __init__(self, *args, **kwargs):
if kwargs['instance']:
Expand Down Expand Up @@ -175,8 +174,9 @@ class Media:


class TaskModelForm(forms.ModelForm):
instructions = forms.CharField(widget=AceWidget(mode='markdown', theme='textmate', width='800px',
height='600px', wordwrap=True))
instructions = (forms.CharField(widget=AceWidget(mode='markdown',
theme='textmate', width='800px', height='600px',
wordwrap=True)))

class Meta:
model = Task
Expand All @@ -186,4 +186,6 @@ class Media:
'all': ('css/admin_ace.css',)
}

instructions.help_text = ('Instructions are written in <a href="http://markdowntutorial.com/" target="_blank">Markdown</a>.')
instructions.help_text = ('Instructions are written in '
'<a href="http://markdowntutorial.com/" '
'target="_blank">Markdown</a>.')
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,6 @@
class Migration(SchemaMigration):

def forwards(self, orm):
# Adding model 'BugzillaField'
db.create_table('tasks_bugzillafield', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=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)),
('creator', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
('name', self.gf('django.db.models.fields.TextField')()),
))
db.send_create_signal('tasks', ['BugzillaField'])

# Adding model 'BugzillaBug'
db.create_table('tasks_bugzillabug', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
Expand All @@ -29,13 +19,24 @@ def forwards(self, orm):
# Adding model 'TaskInvalidationCriterion'
db.create_table('tasks_taskinvalidationcriterion', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('field_name', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['tasks.BugzillaField'])),
('field_value', self.gf('django.db.models.fields.CharField')(max_length=80)),
('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)),
('creator', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
('field_name', self.gf('django.db.models.fields.CharField')(max_length=80)),
('relation', self.gf('django.db.models.fields.IntegerField')(default=0)),
('batch', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['tasks.TaskImportBatch'])),
('field_value', self.gf('django.db.models.fields.CharField')(max_length=80)),
))
db.send_create_signal('tasks', ['TaskInvalidationCriterion'])

# Adding M2M table for field batches on 'TaskInvalidationCriterion'
m2m_table_name = db.shorten_name('tasks_taskinvalidationcriterion_batches')
db.create_table(m2m_table_name, (
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
('taskinvalidationcriterion', models.ForeignKey(orm['tasks.taskinvalidationcriterion'], null=False)),
('taskimportbatch', models.ForeignKey(orm['tasks.taskimportbatch'], null=False))
))
db.create_unique(m2m_table_name, ['taskinvalidationcriterion_id', 'taskimportbatch_id'])

# Adding model 'TaskImportBatch'
db.create_table('tasks_taskimportbatch', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
Expand Down Expand Up @@ -63,17 +64,16 @@ def forwards(self, orm):
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['tasks.TaskImportBatch'], null=True, blank=True),
keep_default=False)


def backwards(self, orm):
# Deleting model 'BugzillaField'
db.delete_table('tasks_bugzillafield')

# Deleting model 'BugzillaBug'
db.delete_table('tasks_bugzillabug')

# Deleting model 'TaskInvalidationCriterion'
db.delete_table('tasks_taskinvalidationcriterion')

# Removing M2M table for field batches on 'TaskInvalidationCriterion'
db.delete_table(db.shorten_name('tasks_taskinvalidationcriterion_batches'))

# Deleting model 'TaskImportBatch'
db.delete_table('tasks_taskimportbatch')

Expand Down Expand Up @@ -130,14 +130,6 @@ def backwards(self, orm):
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'summary': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
'tasks.bugzillafield': {
'Meta': {'object_name': 'BugzillaField'},
'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.TextField', [], {})
},
'tasks.feedback': {
'Meta': {'object_name': 'Feedback'},
'attempt': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['tasks.TaskAttempt']", 'unique': 'True'}),
Expand Down Expand Up @@ -194,10 +186,13 @@ def backwards(self, orm):
},
'tasks.taskinvalidationcriterion': {
'Meta': {'object_name': 'TaskInvalidationCriterion'},
'batch': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['tasks.TaskImportBatch']"}),
'field_name': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['tasks.BugzillaField']"}),
'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': {
Expand Down
Loading

0 comments on commit 0d492af

Please sign in to comment.