#74 Spins: Remove Spins feature from Blockerbugs
Merged 6 years ago by frantisekz. Opened 6 years ago by frantisekz.
fedora-qa/ frantisekz/blockerbugs remove_spins  into  develop

@@ -24,26 +24,24 @@ 

  

  from flask import ext, flash, request, redirect, url_for

  from flask_admin.babel import gettext

- from flask_admin import expose

+ import flask_admin

  import wtforms

  from blockerbugs import app, db

- from blockerbugs.models.spin import Spin

  from blockerbugs.models.build import Build

  from blockerbugs.models.release import Release

  from blockerbugs.models.userinfo import UserInfo

  from blockerbugs.models.milestone import Milestone

  from blockerbugs.controllers.admin.build import BuildView

- from blockerbugs.controllers.admin.spin import SpinView

  from blockerbugs.controllers.admin.auth import FasAuthModelView

  from blockerbugs.controllers.users import check_admin_rights

  

  

- class AdminIndexViewSqla(ext.admin.AdminIndexView):

+ class AdminIndexViewSqla(flask_admin.AdminIndexView):

      def _handle_view(self, name, **kwargs):

          return check_admin_rights()

  

  

- admin = ext.admin.Admin(app, 'Blocker Bug Tracking Admin',

+ admin = flask_admin.Admin(app, 'Blocker Bug Tracking Admin',

                          base_template='admin_layout.html',

                          index_view=AdminIndexViewSqla())

  
@@ -105,7 +103,7 @@ 

              model.bz_user = None

  

  

-     @expose('/generate_api_key/', methods=('GET', ))

+     @flask_admin.expose('/generate_api_key/', methods=('GET', ))

      def generate_api_key(self):

          """

              Generate user api key.
@@ -144,7 +142,6 @@ 

  admin.add_view(MilestoneView(Milestone, db.session))

  admin.add_view(ReleaseView(Release, db.session))

  admin.add_view(UserInfoView(UserInfo, db.session))

- admin.add_view(SpinView(Spin, db.session))

  admin.add_view(BuildView(Build, db.session))

  

  

@@ -46,11 +46,6 @@ 

  

  class BuildForm(wtforms.Form):

      nvr = ReadOnlyTextField('NVR', [wtforms.validators.required()])

-     spins = QuerySelectMultipleField('Spins',

-                                      widget=Select2Widget(multiple=True),

-                                      query_factory=lambda: Spin.query,

-                                      allow_blank=True)

- 

  

  class BuildView(FasAuthModelView):

      column_searchable_list = (Build.nvr, )

@@ -1,205 +0,0 @@ 

- #

- # spin.py - Spin view for admin interface

- #

- # Copyright 2013, Red Hat, Inc

- #

- # This program is free software; you can redistribute it and/or modify

- # it under the terms of the GNU General Public License as published by

- # the Free Software Foundation; either version 2 of the License, or

- # (at your option) any later version.

- #

- # This program is distributed in the hope that it will be useful,

- # but WITHOUT ANY WARRANTY; without even the implied warranty of

- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

- # GNU General Public License for more details.

- #

- # You should have received a copy of the GNU General Public License along

- # with this program; if not, write to the Free Software Foundation, Inc.,

- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

- #

- # Authors:

- #   Tim Flink <tflink@redhat.com>

- #   Ilgiz Islamgulov <ilgizisl@gmail.com>

- import re

- from datetime import datetime

- 

- import koji

- from flask import request, url_for, flash, redirect

- import wtforms

- from flask_admin import expose

- from flask_admin.babel import gettext

- from flask_admin.contrib.sqla.filters import BaseSQLAFilter, FilterConverter

- from flask_admin.contrib.sqla.form import AdminModelConverter

- from flask_admin.form import Select2Field

- from flask_admin.model import filters

- from flask.ext.admin.model.form import converts

- from wtforms import widgets

- 

- from blockerbugs.controllers.admin.auth import FasAuthModelView

- from blockerbugs.models.build import Build

- from blockerbugs.models.spin import Spin, SpinState, SpinType

- 

- 

- class DeclEnumEqualFilter(BaseSQLAFilter):

-     def __init__(self, column, name, options=None, data_type=None):

-         super(DeclEnumEqualFilter, self).__init__(column, name, options,

-                                                   data_type)

- 

-     def get_options(self, view):

-         return [(k, k) for k in self.options.keys()]

- 

-     def apply(self, query, raw_value):

-         value = self.options[raw_value]

-         return query.filter(self.column == value)

- 

-     def operation(self):

-         return gettext('equal')

- 

- 

- class SpinFilterConvert(FilterConverter):

-     _column_types = {

-         'state': SpinState,

-         'spin type': SpinType,

-     }

- 

-     @filters.convert('DeclEnumType')

-     def conv_enum(self, column, name, options=None, **kwargs):

-         if not options:

-             decl_enum = self._column_types[name.lower().strip()]

-             options = dict((unicode(k), v) for k, v in decl_enum._reg.items())

-         name = name.strip()  # flask-admin 1.0.5 does not strip column name

-         return [DeclEnumEqualFilter(column, name, options, **kwargs)]

- 

- 

- class Select2BuildsWidget(widgets.TextInput):

-     def __call__(self, field, **kwargs):

-         kwargs['data-role'] = u'select2Builds'

-         choices = field.iter_choices()

-         kwargs['existing-builds'] = u','.join(choices)

-         return super(Select2BuildsWidget, self).__call__(field, **kwargs)

- 

- 

- class SelectBuildsTagsField(wtforms.Field):

-     widget = Select2BuildsWidget()

- 

-     def __init__(self, *args, **kwargs):

-         self._form = kwargs.get('_form', None)

-         super(SelectBuildsTagsField, self).__init__(*args, **kwargs)

- 

-     def pre_validate(self, form):

-         exist_builds = set(b.nvr for b in Build.query.filter(

-             Build.nvr.in_(self._form_data)).all())

-         for build in self._form_data:

-             if build not in exist_builds:

-                 try:

-                     Build.from_nvr(build)

-                 except koji.GenericError, ex:

-                     self.errors.append('%s: %s' % (build, ex.message))

- 

-     def _get_data(self):

-         if self._form_data is not None:

-             builds = Build.query.filter(Build.nvr.in_(self._form_data)).all()

-             self._set_data(builds)

-         return self._data

- 

-     def _set_data(self, data):

-         self._data = data

-         self._form_data = None

- 

-     data = property(_get_data, _set_data)

- 

-     def process_formdata(self, valuelist):

-         if valuelist:

-             self._data = None

-             self._form_data = list(

-                 v.strip() for v in valuelist[0].split(',') if v.strip())

- 

-     def iter_choices(self):

-         if self._form and self._form.succeeds.data:

-             builds = self._form.succeeds.data.builds

-             return [b.nvr for b in builds]

-         return []

- 

-     def _value(self):

-         if self.raw_data:

-             return u','.join(self.raw_data)

-         else:

-             return u','.join((b.nvr for b in self.data)) if self.data else u''

- 

- 

- class DeclEnumModelConverter(AdminModelConverter):

-     @converts('DeclEnumType')

-     def conv_DeclEnum(self, column, field_args, **extra):

-         field_args['validators'].append(

-             wtforms.validators.AnyOf(column.type.enums))

-         field_args['choices'] = [(f, f) for f in column.type.enums]

-         return Select2Field(**field_args)

- 

- 

- class SpinView(FasAuthModelView):

-     form_excluded_columns = ('date_created', 'date_requested')

-     form_columns = ('name', 'state', 'spin_type', 'url', 'succeeds',

-                     'milestone', 'updates', 'builds')

-     column_searchable_list = [Spin.name]

-     model_form_converter = DeclEnumModelConverter

-     column_filters = (Spin.state, Spin.spin_type, )

-     filter_converter = SpinFilterConvert()

-     form_overrides = {'builds': SelectBuildsTagsField}

-     edit_template = 'admin/spin_edit.html'

-     create_template = 'admin/spin_create.html'

- 

- 

-     _number_parse_re = re.compile(r'(.*?)(\d+)(/|)$')

- 

-     def _fill_new_spin_form(self, form):

-         last_spin = Spin.query.order_by(Spin.date_requested.desc()).first()

-         if last_spin is None:

-             return

-         form.succeeds.data = last_spin

-         if last_spin.spin_type:

-             form.spin_type.data = last_spin.spin_type.description

-         form.milestone.data = last_spin.milestone

-         form.state.data = SpinState.requested.description

-         form.builds.data = last_spin.builds

-         form.updates.data = last_spin.updates

-         try:

-             prefix, numb, _ = re.match(self._number_parse_re,

-                                        last_spin.name).groups()

-             assert numb.isdigit()

-             form.name.data = prefix + str(int(numb) + 1)

-         except AttributeError:

-             form.name.data = ''

-         try:

-             prefix, numb, sl = re.match(self._number_parse_re,

-                                         last_spin.url).groups()

-             assert numb.isdigit()

-             form.url.data = prefix + str(int(numb) + 1) + sl

-         except AttributeError:

-             form.url.data = ''

- 

- 

-     @expose('/new/', methods=('GET', 'POST'))

-     def create_view(self):

-         """

-             Create model view

-             Fill new form with data from previous spin.

-         """

-         return_url = request.args.get('url') or url_for('.index_view')

- 

-         form = self.create_form()

- 

-         if form.validate_on_submit():

-             if self.create_model(form):

-                 if '_add_another' in request.form:

-                     flash(gettext('Model was successfully created.'))

-                     return redirect(url_for('.create_view', url=return_url))

-                 else:

-                     return redirect(return_url)

- 

-         if not form.is_submitted():

-             self._fill_new_spin_form(form)

- 

-         return self.render(self.create_template,

-                            form=form,

-                            form_widget_args=self.form_widget_args,

-                            return_url=return_url)

@@ -32,7 +32,6 @@ 

  from blockerbugs.models.update import Update

  from blockerbugs.models.release import Release

  from blockerbugs.models.bug import Bug

- from blockerbugs.models.spin import Spin, SpinState, SpinType

  import validators

  import errors

  from utils import get_or_404, json_loads, JsonResponse, check_api_rights
@@ -76,29 +75,6 @@ 

      bug_info['type'] = [tp for tp in ACCEPTED_BUGTYPES if getattr(bug, tp)]

      return bug_info

  

- 

- def get_spin_info(spin):

-     spin_simple_fields = ['id', 'name', 'url', 'date_requested',

-                           'date_created', 'spin_type']

-     spin_info = dict(

-         (attr, getattr(spin, attr)) for attr in spin_simple_fields)

-     spin_info['state'] = spin.state.value

-     spin_info['updates'] = [{'title': up.title,

-                              'status': up.status,

-                              'url': up.url} for up in spin.updates]

-     spin_info['builds'] = [{'nvr': build.nvr,

-                             'epoch': build.epoch,

-                             'id': build.koji_id} for build in spin.builds]

-     if spin.succeeds:

-         spin_info['succeeds'] = {'id': spin.succeeds.id,

-                                  'name': spin.succeeds.name}

-     else:

-         spin_info['succeeds'] = {}

-     spin_info['milestone'] = {'release': spin.milestone.release.number,

-                               'version': spin.milestone.version}

-     return spin_info

- 

- 

  @api_v0.route('/milestones/<int:rel_num>/<milestone_version>/updates')

  def list_updates(rel_num, milestone_version):

      release = get_or_404(Release, number=rel_num)
@@ -136,105 +112,6 @@ 

      bugs_info = [get_bug_info(bug) for bug in bugs]

      return JsonResponse(bugs_info)

  

- 

- @api_v0.route('/milestones/<int:rel_num>/<milestone_version>/spins')

- def list_spins(rel_num, milestone_version):

-     release = get_or_404(Release, number=rel_num)

-     milestone = get_or_404(Milestone, release=release,

-                            version=milestone_version)

-     spins = milestone.spins.order_by(Spin.date_requested.desc())

-     spins_info = [get_spin_info(spin) for spin in spins]

-     return JsonResponse(spins_info)

- 

- 

- @api_v0.route('/milestones/<int:rel_num>/<milestone_version>/spins',

-               methods=['POST'])

- @check_api_rights

- def create_spin(rel_num, milestone_version):

-     spin_validator = validators.DictValidator({

-         'name': validators.StringValidator(),

-         'spin_type': validators.ChoicesValidator(SpinType.values()),

-         'updates': validators.ListValidator(basestring),

-         'succeeds': validators.IntegerValidator(required=False),

-         'state': validators.ChoicesValidator(SpinState.values(),

-                                              required=False),

-         'url': validators.UrlValidator(required=False)

-     })

-     release = get_or_404(Release, number=rel_num)

-     milestone = get_or_404(Milestone, release=release,

-                            version=milestone_version)

-     data = json_loads(request.data)

-     spin_validator(data)

-     spin_kwargs = {

-         'name': data['name'],

-         'spin_type': data['spin_type'],

-         'updates': [get_or_404(Update, title=tit) for tit in data['updates']],

-         'milestone': milestone,

-         'date_requested': datetime.datetime.utcnow(),

-     }

-     if 'succeeds' in data:

-         spin_kwargs['succeeds'] = get_or_404(Spin, id=data['succeeds'])

-     if 'state' in data:

-         spin_kwargs['state'] = data['state']

-     else:

-         spin_kwargs['state'] = SpinState.requested

-     if 'url' in data:

-         spin_kwargs['url'] = data['url']

-     spin = Spin(**spin_kwargs)

-     db.session.add(spin)

-     db.session.commit()

-     location_str = 'milestone/{m.release.number}/{m.version}/spins/{spin.id}'

-     location_header = {'Location': location_str.format(m=milestone, spin=spin)}

-     return JsonResponse(status=201, headers=location_header)

- 

- 

- @api_v0.route(

-     '/milestones/<int:rel_num>/<milestone_version>/spins/<int:spin_id>')

- def get_spin(rel_num, milestone_version, spin_id):

-     release = get_or_404(Release, number=rel_num)

-     milestone = get_or_404(Milestone,

-                            release=release, version=milestone_version)

-     spin = get_or_404(Spin, id=spin_id)

-     if spin.milestone != milestone:

-         raise errors.NoSuchObjectError(obj_type='Spin')

-     spin_info = get_spin_info(spin)

-     return JsonResponse(spin_info)

- 

- 

- @api_v0.route(

-     '/milestones/<int:rel_num>/<milestone_version>/spins/<int:spin_id>',

-     methods=['PUT'])

- @check_api_rights

- def update_spin(rel_num, milestone_version, spin_id):

-     spin_validator = validators.DictValidator({

-         'name': validators.StringValidator(required=False),

-         'spin_type': validators.ChoicesValidator(SpinType.values(),

-                                                  required=False),

-         'state': validators.ChoicesValidator(SpinState.values(),

-                                              required=False),

-         'url': validators.UrlValidator(required=False),

-         'date_created': validators.DateTimeValidator(required=False)

-     })

-     release = get_or_404(Release, number=rel_num)

-     milestone = get_or_404(Milestone, release=release,

-                            version=milestone_version)

-     spin = get_or_404(Spin, id=spin_id)

-     if spin.milestone != milestone:

-         raise errors.NoSuchObjectError(obj_type='Spin')

-     data = json_loads(request.data)

-     spin_validator(data)

-     if 'state' in data:

-         spin.state = data['state']

-     if 'name' in data:

-         spin.name = data['name']

-     if 'spin_type' in data:

-         spin.spin_type = data['spin_type']

-     if 'date_created' in data:

-         spin.date_created = parse_date(data['date_created'])

-     db.session.add(spin)

-     db.session.commit()

-     return JsonResponse(status=200)

- 

  @api_v0.route('/milestones/current')

  def get_current_milestone():

      current_milestone = get_or_404(Milestone, current=True)

@@ -33,7 +33,6 @@ 

  from blockerbugs.models.update import Update

  from blockerbugs.models.release import Release

  from blockerbugs.models.userinfo import UserInfo

- from blockerbugs.models.spin import Spin, SpinState

  from blockerbugs.controllers.forms import BugProposeForm, FasBugzillaForm

  

  main = Blueprint('main', __name__)
@@ -171,8 +170,7 @@ 

  def index():

      if app.debug:

          app.logger.debug('rendering index')

-     spins = Spin.query.filter_by(state=SpinState.built).order_by(Spin.date_requested.desc()).limit(3)

-     return render_template('index.html', title='Fedora Blocker Bugs', spins=spins)

+     return render_template('index.html', title='Fedora Blocker Bugs')

  

  

  @main.route('/<int:num>/<release_name>')
@@ -285,20 +283,6 @@ 

                             bz_url=app.config['BUGZILLA_URL'])

  

  

- @main.route('/milestone/<int:num>/<milestone_name>/spins')

- def display_spins(num, milestone_name):

-     release = Release.query.filter_by(number=num).first()

-     milestone = Milestone.query.filter_by(release=release, version=milestone_name).first()

-     if not milestone:

-         abort(404)

-     milestone_info = get_milestone_info(milestone)

-     spins = milestone.spins.order_by(Spin.date_requested.desc()).all()

-     return render_template('spin_list.html', spins=spins,

-                            info=milestone_info,

-                            build_url = app.config['KOJI_URL'] + 'koji/buildinfo?buildID=',

-                            title="Fedora %s %s Spins" % (milestone_info['number'], milestone_info['phase']))

- 

- 

  @main.route('/milestone/<int:num>/<milestone_name>/stats')

  def display_stats(num, milestone_name):

      release = Release.query.filter_by(number=num).first()

file modified
+1 -11
@@ -27,17 +27,9 @@ 

  from blockerbugs import db

  from blockerbugs.util.koji_interface import get_build_data

  

- build_spins = db.Table(

-     'build_spins',

-     db.Column('spin_id', db.Integer, db.ForeignKey('spin.id')),

-     db.Column('build_id', db.Integer, db.ForeignKey('build.koji_id'))

- )

- 

  

  class Build(db.Model):

      koji_id = db.Column(db.Integer, primary_key=True)

-     spins = db.relationship('Spin', secondary=build_spins,

-                             backref='builds')

      pkg_name = db.Column(db.Text)

      version = db.Column(db.Text)

      release = db.Column(db.Text)
@@ -51,7 +43,7 @@ 

          return 'build: %s' % (self.nvr)

  

      @classmethod

-     def from_nvr(cls, nvr, spins=None):

+     def from_nvr(cls, nvr):

          data = get_build_data(nvr)

          exists_build = Build.query.get(data['build_id'])

          if exists_build is not None:
@@ -63,7 +55,5 @@ 

          build.release = data['release']

          build.version = data['version']

          build.nvr = '{b.pkg_name}-{b.version}-{b.release}'.format(b=build)

-         if spins is not None:

-             build.spins = spins

          db.session.add(build)

          db.session.commit()

@@ -1,95 +0,0 @@ 

- #

- # spin.py - database model for spins

- #

- # Copyright 2012, Red Hat, Inc

- #

- # This program is free software; you can redistribute it and/or modify

- # it under the terms of the GNU General Public License as published by

- # the Free Software Foundation; either version 2 of the License, or

- # (at your option) any later version.

- #

- # This program is distributed in the hope that it will be useful,

- # but WITHOUT ANY WARRANTY; without even the implied warranty of

- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

- # GNU General Public License for more details.

- #

- # You should have received a copy of the GNU General Public License along

- # with this program; if not, write to the Free Software Foundation, Inc.,

- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

- #

- # Authors:

- #   Tim Flink <tflink@redhat.com>

- from datetime import datetime

- 

- from sqlalchemy.ext.hybrid import hybrid_property

- 

- from blockerbugs import db

- from blockerbugs.models import DeclEnum

- 

- used_updates = db.Table(

-     'used_updates',

-     db.Column('update_id', db.Integer, db.ForeignKey('update.id')),

-     db.Column('spin_id', db.Integer, db.ForeignKey('spin.id'))

- )

- 

- 

- class SpinState(DeclEnum):

-     requested = 'requested', u'requested'

-     built = 'built', u'built'

-     obsolete = 'obsolete', u'obsolete'

- 

- 

- class SpinType(DeclEnum):

-     smoke = 'smoke', u'smoke'

-     TC = 'TC', u'TC'

-     RC = 'RC', u'RC'

- 

- class Spin(db.Model):

-     id = db.Column(db.Integer, primary_key=True)

-     name = db.Column(db.String(160), unique=False)

-     _state = db.Column('state', SpinState.db_type())

-     url = db.Column(db.String(2048), nullable=True)

-     date_requested = db.Column(db.DateTime, unique=False,

-                                default=datetime.utcnow)

-     date_created = db.Column(db.DateTime, unique=False)

-     _spin_type = db.Column('spin_type', SpinType.db_type())

-     updates = db.relationship('Update', secondary=used_updates,

-                               backref=db.backref('spins', lazy='dynamic'))

-     succeeds_id = db.Column(db.Integer, db.ForeignKey('spin.id'),

-                             nullable=True)

-     succeeded_by = db.relationship('Spin', backref=db.backref('succeeds',

-                                                           remote_side=id,

-                                                           uselist=False))

-                             #lazy='dynamic')

-     milestone_id = db.Column(db.Integer, db.ForeignKey('milestone.id'))

-     milestone = db.relationship('Milestone',

-                                 backref=db.backref('spins', lazy='dynamic'))

- 

-     @hybrid_property

-     def state(self):

-         return self._state

- 

-     @state.setter

-     def state(self, state):

-         if isinstance(state, basestring):

-             state = SpinState.from_string(state)

-         if self._state == state:

-             return

-         self._state = state

-         if state == SpinState.requested:

-             self.date_requested = datetime.utcnow()

-         if state == SpinState.built and self.date_created is None:

-             self.date_created = datetime.utcnow()

- 

-     @hybrid_property

-     def spin_type(self):

-         return self._spin_type

- 

-     @spin_type.setter

-     def spin_type(self, spin_type):

-         if isinstance(spin_type, basestring):

-             spin_type = SpinType.from_string(spin_type)

-         self._spin_type = spin_type

- 

-     def __str__(self):

-         return 'spin: %s' % (self.name)

@@ -47,32 +47,6 @@ 

                  </ul>

              </div>

          </div>

-         <div class="six columns" id="right-submain">

-             <div class="panel radius" id="right-submain-info">

-                 <h3>Most Recent Spins</h3>

-                 <table width=100%>

-                     <thead>

-                         <th>Name</th>

-                         <th>Type</th>

-                     </thead>

-                     <tbody>

-                         {% for spin in spins %}

-                         <tr>

-                             <td><a href="{{ spin.url }}">{{ spin.name }}</a></td>

-                             <td>{{ spin.spin_type }}</td>

-                         </tr>

-                         {% endfor %}

-                     </tbody>

-                 </table>

-                 {#

-                 <ul>

-                     {% for spin in spins %}

-                     <li>{{ spin.name }}</li>

-                     {% endfor %}

-                 </ul>

-                 #}

-             </div>

-         </div>

      </div>

  </div>

  {% endblock %}

@@ -13,7 +13,6 @@ 

                  <li><a {{ subnavactive('info') }} href="./info">Info</a></li>

                  <li><a {{ subnavactive('buglist') }} href="./buglist">Bug List</a></li>

                  <li><a {{ subnavactive('updates') }} href="./updates">Updates</a></li>

-                 <li><a {{ subnavactive('spins') }} href="./spins">Spins</a></li>

                  <li><a {{ subnavactive('stats') }} href="./stats">Stats</a></li>

                  <li><a {{ subnavactive('irc') }} href="./irc">IRC Format</a></li>

                  <li><a {{ subnavactive('irc') }} href="./requests">Requests</a></li>

@@ -1,79 +0,0 @@ 

- {% extends "milestone_base.html" %}

- 

- {% block jsheader %}

-     <script type="text/javascript">

-         $(document).ready(function()

-                 {

-                 $("#spinUpdates").tablesorter();

-         });

-     </script>

- 

-     {% endblock %}

- 

- {# this should be imported from somewhere else, not pasted #}

- {% macro statustext(update) %}

- {% if update %}

- {% if update.pending %}

- pending

- {% endif %}

- {{ update.status }}

- {% endif %}

- {% endmacro %}

- 

- 

- 

- {% block body %}

- 

- 

- <div class="row">

-     <div class="twelve columns" id="spin-info">

-         {% for spin in spins %}

-         <div class="row">

-             <h3>{{ spin.name }}</h3>

-             <div class="nine columns">

-                 <ul>

-                     <li>State: {{ spin.state.name }}</li>

-                     <li>Date Requested: {{ spin.date_requested |datetime}}</li>

-                     <li>Type: {{ spin.spin_type }}</li>

-                     <li>Location: <a href="{{ spin.url }}">{{ spin.url }}</a></li>

-                     <li>Builds: {% for build in spin.builds %}

-                             <a href={{build_url}}{{ build.koji_id }} style="white-space: pre;">{{ build.nvr }}, </a>

-                         {% endfor %}

-                     </li>

-                 </ul>

-                 <h5>Updates in Spin</h5>

-                 <table id="spinUpdates" cellspacing="1" class="tablesorter {sortlist: [[2,0],[1,0]]}">

-                     <thead>

-                         <tr>

-                             <th>Type</th>

-                             <th>Component</th>

-                             <th>Title</th>

-                             <th>Status</th>

-                         </tr>

-                     </thead>

-                     <tbody>

-                         {% for update in spin.updates %}

-                             <tr>

-                                 <td>{{ update | updatetype }}</td>

-                                 <td>{{ update.bugs[0].component }}</td>

-                                 <td><a href='{{ update.url }}'>{{ update.title }}</a></td>

-                                 <td>{{ statustext(update) }}</td>

-                             </tr>

-                         {% else %}

-                             <tr>

-                                 <td></td>

-                                 <td></td>

-                                 <td></td>

-                                 <td></td>

-                             </tr>

-                         {% endfor %}

-                     </tbody>

-                 </table>

-             </div>

-         </div>

-         {% else %}

-         <h3>No spins yet for this milestone</h3>

-         {% endfor %}

-     </div> <!-- end of 12 columns -->

- </div> <!-- end of row -->

- {% endblock %}

file modified
+1 -161
@@ -6,11 +6,10 @@ 

  

  from blockerbugs import db

  from blockerbugs import app

- from blockerbugs.models.spin import Spin, SpinState, SpinType

  from blockerbugs.models.build import Build

  from blockerbugs.models.userinfo import UserInfo

  from testing.test_controllers import add_release, add_milestone, \

-     add_bug, add_spin, add_update

+     add_bug, add_update

  from blockerbugs.controllers.api import errors

  

  
@@ -41,19 +40,9 @@ 

          cls.update_testing2 = add_update(u'test-testing2.fc99', u'testing',

                                           [bug2],

                                           cls.release, [cls.milestone])

-         cls.spin2 = add_spin('test-spin2', SpinType.TC, [],

-                              cls.milestone, state=SpinState.requested)

- 

          build = Build()

          build.koji_id = 33

          build.nvr = 'libofx-0.9.9-1.fc20'

-         cls.spin1 = add_spin('test-spin1', SpinType.TC,

-                              [cls.update_pending_stable],

-                              cls.milestone, state=SpinState.requested)

-         cls.spin1.builds = [build]

-         cls.spin1.date_requested = datetime(1990, 1, 1)

-         cls.spin1.succeeds = cls.spin2

-         cls.spin2.date_requested = datetime(2000, 1, 1)

          cls.api_user = UserInfo('api_user')

          passwd = cls.api_user.generate_api_key()

          cls.headers = {'X-Auth-User': 'api_user', 'X-Auth-Key': passwd}
@@ -133,102 +122,6 @@ 

          update = data[0]

          assert update['title'] == u'test-testing2.fc99'

  

-     def test_list_spins(self):

-         url = '/api/v0/milestones/99/final/spins'

-         resp = self.client.get(url)

-         assert resp.status_code == httplib.OK

-         data = json.loads(resp.data)

-         assert len(data) == 2

-         spin = Spin.query.filter_by(name='test-spin1').first()

-         resp_spin = data[1]

-         assert resp_spin['name'] == spin.name

-         assert resp_spin['id'] == spin.id

-         assert resp_spin['state'] == spin.state.value

-         assert resp_spin['url'] == spin.url

-         assert resp_spin['date_requested'] == datetime(1990, 1, 1).isoformat()

-         assert resp_spin['date_created'] is None

-         assert resp_spin['spin_type'] == spin.spin_type.value

-         assert len(resp_spin['updates']) == 1

-         assert {'title': u'test-pending-stable.fc99',

-                 'status': u'stable',

-                 'url': u'http://localhost/update'} in resp_spin['updates']

-         succeeded = Spin.query.filter_by(name='test-spin2').first()

-         assert resp_spin['succeeds']['name'] == succeeded.name

-         assert resp_spin['succeeds']['id'] == succeeded.id

-         assert {'version': 'final', 'release': 99} == resp_spin['milestone']

- 

-     def test_basic_create_spins(self):

-         url = '/api/v0/milestones/99/final/spins'

-         test_spin_data = {

-             'name': 'create_spin_test1',

-             'spin_type': 'TC',

-             'updates': ['test-pending-stable.fc99'],

-         }

-         with patch('blockerbugs.models.spin.datetime') as mock_date:

-             mock_date.utcnow.return_value = datetime(1990, 1, 1)

-             mock_date.side_effect = lambda *args, **kw: datetime(*args, **kw)

-             resp = self.client.post(url,

-                                     data=json.dumps(test_spin_data),

-                                     content_type='application/json',

-                                     headers=self.headers)

-         assert resp.status_code == httplib.CREATED

-         assert 'Location' in resp.headers

-         spin = Spin.query.filter_by(name='create_spin_test1').first()

-         assert spin.name == 'create_spin_test1'

-         assert spin.state == SpinState.requested

-         assert spin.url is None

-         assert spin.spin_type == SpinType.TC

-         assert len(spin.updates) == 1

-         assert spin.updates[0].title == 'test-pending-stable.fc99'

-         assert spin.succeeds is None

-         assert spin.milestone.version == 'final'

-         assert spin.milestone.release.number == 99

-         assert spin.date_requested == datetime(1990, 1, 1)

- 

-     def test_create_spin_fails_unknown_update_title(self):

-         url = '/api/v0/milestones/99/final/spins'

-         test_spin_data = {

-             'name': 'create_spin_test1',

-             'spin_type': 'TC',

-             'updates': ['test-pending-stable.fc99', 'unknown'],

-         }

-         resp = self.client.post(url,

-                                 data=json.dumps(test_spin_data),

-                                 content_type='application/json',

-                                 headers=self.headers)

-         assert resp.status_code == httplib.NOT_FOUND

-         error = json.loads(resp.data)['error']

-         assert error['code'] == errors.NoSuchObjectError.code

-         assert 'Update' in error['message']

- 

-     def test_invalid_spin_name(self):

-         url = '/api/v0/milestones/99/final/spins'

-         test_spin_data = {

-             'name': 123,

-             'spin_type': 'create_test1',

-             'updates': ['test-pending-stable.fc99', 'unknown'],

-             'succeeds': []

-         }

-         resp = self.client.post(url,

-                                 data=json.dumps(test_spin_data),

-                                 content_type='application/json',

-                                 headers=self.headers)

-         assert resp.status_code == httplib.BAD_REQUEST

-         error = json.loads(resp.data)['error']

-         assert error['code'] == errors.ValidationError.code

-         assert 'name' in error['message']

- 

-     def test_malformed_spin_data_request(self):

-         url = '/api/v0/milestones/99/final/spins'

-         test_spin_data = "{'name'= 123}"

-         resp = self.client.post(url,

-                                 data=test_spin_data,

-                                 content_type='application/json',

-                                 headers=self.headers)

-         assert resp.status_code == httplib.BAD_REQUEST

-         error = json.loads(resp.data)['error']

-         assert error['code'] == 1003

- 

      def test_bad_bugtype_list_bugs(self):

          url = '/api/v0/milestones/99/final/updates?bugtype=foo&'

          resp = self.client.get(url)
@@ -237,59 +130,6 @@ 

          assert error['code'] == errors.InvalidArgumentError.code

          assert 'bugtype' in error['message']

  

-     def test_get_specific_spin_info(self):

-         spin = Spin.query.filter_by(name='test-spin1').first()

-         url = '/api/v0/milestones/99/final/spins/%d' % (spin.id)

-         resp = self.client.get(url)

-         assert resp.status_code == httplib.OK

-         resp_spin = json.loads(resp.data)

-         assert resp_spin['name'] == spin.name

-         assert resp_spin['id'] == spin.id

-         assert resp_spin['state'] == spin.state.value

-         assert resp_spin['url'] == spin.url

-         assert resp_spin['date_requested'] == datetime(1990, 1, 1).isoformat()

-         assert resp_spin['date_created'] is None

-         assert resp_spin['spin_type'] == spin.spin_type.value

-         assert len(resp_spin['builds']) == 1

-         build = resp_spin['builds'][0]

-         assert build['id'] == spin.builds[0].koji_id

-         assert build['nvr'] == spin.builds[0].nvr

-         assert build['epoch'] == spin.builds[0].epoch

- 

-     def test_update_spin_created_date(self):

-         spin_id = Spin.query.filter_by(name='test-spin2').first().id

-         url = '/api/v0/milestones/99/final/spins/%d' % (spin_id)

-         date_created = datetime(1991, 1, 1)

-         update_data = {'date_created': date_created.isoformat(), }

-         resp = self.client.put(url, data=json.dumps(update_data),

-                                content_type='application/json',

-                                headers=self.headers)

-         assert resp.status_code == httplib.OK

-         updated_spin = Spin.query.get(spin_id)

-         assert updated_spin.date_created == date_created

- 

-     def test_update_spin_invalid_created_date(self):

-         spin_id = Spin.query.filter_by(name='test-spin2').first().id

-         url = '/api/v0/milestones/99/final/spins/%d' % (spin_id)

-         date_created = 'invalid'

-         update_data = {'date_created': date_created, }

-         resp = self.client.put(url, data=json.dumps(update_data),

-                                content_type='application/json',

-                                headers=self.headers)

-         assert resp.status_code == httplib.BAD_REQUEST

-         error = json.loads(resp.data)['error']

-         assert error['code'] == errors.ValidationError.code

- 

-     def test_update_spin_auth_failed(self):

-         spin_id = Spin.query.filter_by(name='test-spin2').first().id

-         url = '/api/v0/milestones/99/final/spins/%d' % (spin_id)

-         update_data = {'date_created': 'foo', }

-         resp = self.client.put(url, data=json.dumps(update_data),

-                                content_type='application/json')

-         assert resp.status_code == httplib.FORBIDDEN

-         error = json.loads(resp.data)['error']

-         assert error['code'] == errors.AuthFailedError.code

- 

      def test_get_current_milestone(self):

          url = '/api/v0/milestones/current'

          resp = self.client.get(url)

@@ -5,7 +5,6 @@ 

  from blockerbugs.models.bug import Bug

  from blockerbugs.models.update import Update

  from blockerbugs.models.criterion import Criterion

- from blockerbugs.models.spin import Spin, SpinState, SpinType

  from blockerbugs import db

  from blockerbugs import app

  from blockerbugs.controllers import main
@@ -27,18 +26,6 @@ 

      return test_milestone

  

  

- def add_spin(name, spin_type, updates, milestone, state=SpinState.built):

-     test_spin = Spin()

-     test_spin.name = name

-     test_spin.spin_type = spin_type

-     test_spin.updates = updates

-     test_spin.milestone = milestone

-     test_spin.state = state

-     db.session.add(test_spin)

-     db.session.commit()

-     return test_spin

- 

- 

  def add_bug(bugid, summary, milestone):

      test_bug = Bug(bugid,

                     'https://bugzilla.redhat.com/show_bug.cgi?id=%d' % bugid,
@@ -89,7 +76,6 @@ 

          final_milestone = add_milestone(release, 'final', 100, 101,

                                          '99-final', True)

          add_milestone(release, 'beta', 200, 201, '99-beta')

-         add_spin('test-spin1', SpinType.smoke, [], final_milestone)

  

      @classmethod

      def teardown_class(cls):
@@ -101,7 +87,6 @@ 

          assert rv.status_code == 200

          assert 'Fedora 99 final' in rv.data

          assert 'Fedora 99 beta' not in rv.data

-         assert 'test-spin1' in rv.data

  

      def test_display_buglist_returns_200code(self):

          rv = self.client.get('milestone/99/final/buglist')
@@ -119,14 +104,6 @@ 

          rv = self.client.get('milestone/99/final/info')

          assert rv.status_code == 200

  

-     def test_display_stats_returns_200code(self):

-         rv = self.client.get('milestone/99/final/spins')

-         assert rv.status_code == 200

- 

-     def test_display_spins_returns_200code(self):

-         rv = self.client.get('milestone/99/final/spins')

-         assert rv.status_code == 200

- 

  

  class TestSyncedMilestone(object):

      @classmethod
@@ -139,8 +116,6 @@ 

          final_milestone = add_milestone(release, 'final', 100, 101,

                                          '99-final', True)

          add_milestone(release, 'beta', 200, 201, '99-beta')

-         add_spin('test-spin1', SpinType.smoke, [], final_milestone,

-                  SpinState.built)

          add_bug(9000, 'testbug1', final_milestone)

          add_bug(9001, 'testbug2', final_milestone)

          final_milestone.last_bug_sync = datetime.datetime.utcnow()
@@ -156,7 +131,6 @@ 

          assert rv.status_code == 200

          assert 'Fedora 99 final' in rv.data

          assert 'Fedora 99 beta' not in rv.data

-         assert 'test-spin1' in rv.data

  

      def test_display_milestone_redirect(self):

          rv = self.client.get('milestone/99/final')
@@ -178,11 +152,6 @@ 

          assert '9000' in rv.data

          assert 'testbug2' in rv.data

  

-     def test_display_spins(self):

-         rv = self.client.get('milestone/99/final/spins')

-         assert rv.status_code == 200

-         assert 'test-spin1' in rv.data

- 

      def test_display_info(self):

          rv = self.client.get('milestone/99/final/info')

          assert rv.status_code == 200
@@ -204,8 +173,6 @@ 

          cls.milestone = add_milestone(cls.release, 'final', 100, 101,

                                          '99-final', True)

          add_milestone(cls.release, 'beta', 200, 201, '99-beta')

-         add_spin('test-spin1', SpinType.TC, [], cls.milestone,

-                  state=SpinState.built)

          bug1 = add_bug(9000, 'testbug1', cls.milestone)

          bug2 = add_bug(9001, 'testbug2', cls.milestone)

          bug2.accepted_blocker = False

@@ -1,83 +0,0 @@ 

- from datetime import datetime

- 

- from mock import patch

- import pytest

- 

- from blockerbugs import db

- from blockerbugs.models.spin import Spin, SpinState, SpinType

- from testing.test_controllers import add_release, add_milestone, \

-     add_bug, add_spin

- 

- 

- class TestSpinModel(object):

-     @classmethod

-     def setup_class(cls):

-         cls.mocked_date = datetime(1990, 1, 1)

-         patcher_config = {'utcnow.return_value': cls.mocked_date,

-                           'side_effect': lambda *args, **kw: datetime(*args, **kw)}

-         cls.patcher = patch('blockerbugs.models.spin.datetime', **patcher_config)

-         cls.patcher.start()

-         db.session.rollback()

-         db.drop_all()

-         db.create_all()

-         cls.release = add_release(99)

-         cls.milestone = add_milestone(cls.release, 'final', 100, 101,

-                                        '99-final', True)

- 

-     @classmethod

-     def teardown_class(cls):

-         cls.patcher.stop()

-         db.session.rollback()

-         db.drop_all()

- 

-     def test_set_date_requested_default(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                          self.milestone, state=SpinState.obsolete)

-         assert spin.date_requested is not None

- 

-     def test_set_date_requested_then_state_requested(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                         self.milestone, state=SpinState.obsolete)

-         spin.date_requested = datetime(2001, 1, 1)

-         spin.state = SpinState.requested

-         assert spin.date_requested == self.mocked_date

- 

-     def test_set_date_created_is_none_and_state_built(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                         self.milestone, state=SpinState.obsolete)

-         assert spin.date_created is None

-         spin.state = SpinState.built

-         assert spin.date_created == self.mocked_date

- 

-     def test_state_built_and_date_created_is_not_null(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                         self.milestone, state=SpinState.obsolete)

-         dt = datetime(2001, 1, 1)

-         spin.date_created = dt

-         spin.state = SpinState.built

-         assert spin.date_created == dt

- 

-     def test_spin_state_from_string(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                         self.milestone, state=SpinState.obsolete)

-         spin.state = 'built'

-         assert SpinState.built == spin.state

- 

-     def test_spin_type_from_string(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                         self.milestone, state=SpinState.obsolete)

-         spin.spin_type = 'RC'

-         assert spin.spin_type == SpinType.RC

- 

-     def test_state_from_bad_string(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                         self.milestone, state=SpinState.obsolete)

-         with pytest.raises(ValueError):

-             spin.state = 'foo'

- 

- 

-     def test_spin_type_from_bad_string(self):

-         spin = add_spin('test-spin', SpinType.TC, [],

-                         self.milestone, state=SpinState.obsolete)

-         with pytest.raises(ValueError):

-             spin.spin_type = 'foo'

@@ -5,7 +5,6 @@ 

  from blockerbugs.models.bug import Bug

  from blockerbugs.models.update import Update

  from blockerbugs.models.criterion import Criterion

- from blockerbugs.models.spin import Spin, SpinType

  from blockerbugs import db

  

  
@@ -176,37 +175,3 @@ 

          assert criterion.number == ref_number

          assert criterion.current == ref_current

          assert criterion.milestone == self.ref_milestone

- 

- 

-     def test_add_spin(self):

-         ref_bugs = [generate_bug(123456, 'testbug1', self.ref_milestone),

-                     generate_bug(234567, 'testbug2', self.ref_milestone)]

- 

-         ref_updates = [generate_update('test-1.0-1.fc99', 'PENDING_STABLE',

-                                        ref_bugs, self.ref_release,

-                                        [self.ref_milestone])]

- 

-         db.session.add(self.ref_release)

-         db.session.add(self.ref_milestone)

-         db.session.add(ref_bugs[0])

-         db.session.add(ref_bugs[1])

-         db.session.add(ref_updates[0])

-         db.session.commit()

- 

-         ref_name = 'test-spin1'

- 

-         test_spin = Spin()

-         test_spin.name = ref_name

-         test_spin.spin_type = SpinType.smoke

-         test_spin.updates = ref_updates

-         test_spin.milestone = self.ref_milestone

-         db.session.add(test_spin)

-         db.session.commit()

- 

-         spins = Spin.query.filter_by(name=ref_name).all()

-         assert len(spins) == 1

-         spin = spins[0]

-         assert spin.name == ref_name

-         assert spin.spin_type == SpinType.smoke

-         assert ref_updates[0] in spin.updates

-