Repository: fedora-infra/bodhi

Issue:
The relationship between Packages and Users is unused
I noticed today while writing docblocks for the models that the Packages.committers relationship is always empty. To confirm this, I examined the database dump, and found that there really are no rows in the ```user_package_table``` through table:

```
--
-- Data for Name: user_package_table; Type: TABLE DATA; Schema: public; Owner: bodhi2
--

COPY user_package_table (user_id, package_id) FROM stdin;
\.
```

We should delete this relationship and table.


Discussion:

Resolving patch:
diff --git a/bodhi/server/migrations/versions/7ba286412ad4_drop_the_relationship_between_packages_.py b/bodhi/server/migrations/versions/7ba286412ad4_drop_the_relationship_between_packages_.py
new file mode 100644
--- /dev/null
+++ b/bodhi/server/migrations/versions/7ba286412ad4_drop_the_relationship_between_packages_.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2019 Sebastian Wojciechowski.
+#
+# This file is part of Bodhi.
+#
+# 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.
+"""
+Drop the relationship between Packages and Users.
+
+Revision ID: 7ba286412ad4
+Revises: 2fc96aa44a74
+Create Date: 2019-02-16 08:36:48.665825
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = '7ba286412ad4'
+down_revision = '2fc96aa44a74'
+
+
+def upgrade():
+    """Drop the user_package_table table and associations."""
+    op.drop_table('user_package_table')
+
+
+def downgrade():
+    """Bring back the user_package_table table and associations."""
+    op.create_table(
+        'user_package_table',
+        sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True),
+        sa.Column('package_id', sa.INTEGER(), autoincrement=False, nullable=True),
+        sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='user_package_table_user_id_fkey'),
+        sa.ForeignKeyConstraint(['package_id'], ['packages.id'],
+                                name='user_package_table_package_id_fkey'))
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -722,11 +722,6 @@ class ComposeState(DeclEnum):
     Column('update_id', Integer, ForeignKey('updates.id')),
     Column('bug_id', Integer, ForeignKey('bugs.id')))
 
-user_package_table = Table(
-    'user_package_table', metadata,
-    Column('user_id', Integer, ForeignKey('users.id')),
-    Column('package_id', Integer, ForeignKey('packages.id')))
-
 
 class Release(Base):
     """
@@ -933,13 +928,11 @@ class to create when loading rows from the database.
         builds (sqlalchemy.orm.collections.InstrumentedList): A list of :class:`Build` objects.
         test_cases (sqlalchemy.orm.collections.InstrumentedList): A list of :class:`TestCase`
             objects.
-        committers (sqlalchemy.orm.collections.InstrumentedList): A list of :class:`User` objects
-            who are committers.
     """
 
     __tablename__ = 'packages'
     __get_by__ = ('name',)
-    __exclude_columns__ = ('id', 'committers', 'test_cases', 'builds',)
+    __exclude_columns__ = ('id', 'test_cases', 'builds',)
 
     name = Column(UnicodeText, nullable=False)
     requirements = Column(UnicodeText)
@@ -947,8 +940,6 @@ class to create when loading rows from the database.
 
     builds = relationship('Build', backref=backref('package', lazy='joined'))
     test_cases = relationship('TestCase', backref='package', order_by="TestCase.id")
-    committers = relationship('User', secondary=user_package_table,
-                              backref='packages')
 
     __mapper_args__ = {
         'polymorphic_on': type,
@@ -3074,12 +3065,7 @@ def get_maintainers(self):
             list: A list of :class:`Users <User>` who have commit access to all of the
                 packages that are contained within this update.
         """
-        people = set([self.user])
-        for build in self.builds:
-            if build.package.committers:
-                for committer in build.package.committers:
-                    people.add(committer)
-        return list(people)
+        return [self.user]
 
     @property
     def product_version(self):
@@ -4070,7 +4056,7 @@ class User(Base):
     """
 
     __tablename__ = 'users'
-    __exclude_columns__ = ('comments', 'updates', 'packages', 'buildroot_overrides')
+    __exclude_columns__ = ('comments', 'updates', 'buildroot_overrides')
     __include_extras__ = ('avatar', 'openid')
     __get_by__ = ('name',)
 
diff --git a/bodhi/server/services/user.py b/bodhi/server/services/user.py
--- a/bodhi/server/services/user.py
+++ b/bodhi/server/services/user.py
@@ -24,9 +24,8 @@
 from sqlalchemy import func, distinct
 from sqlalchemy.sql import or_
 
-from bodhi.server.models import Group, Package, Update, User
-from bodhi.server.validators import (validate_updates,
-                                     validate_packages, validate_groups)
+from bodhi.server.models import Group, Update, User
+from bodhi.server.validators import (validate_updates, validate_groups)
 import bodhi.server.schemas
 import bodhi.server.security
 import bodhi.server.services.errors
@@ -94,7 +93,6 @@ def get_user(request):
     colander_querystring_validator,
     validate_groups,
     validate_updates,
-    validate_packages,
 )
 
 
@@ -156,11 +154,6 @@ def query_users(request):
         args = [Update.alias == update.alias for update in updates]
         query = query.filter(or_(*args))
 
-    packages = data.get('packages')
-    if packages is not None:
-        query = query.join(User.packages)
-        query = query.filter(or_(*[Package.id == p.id for p in packages]))
-
     # We can't use ``query.count()`` here because it is naive with respect to
     # all the joins that we're doing above.
     count_query = query.with_labels().statement\


Test patch:
diff --git a/bodhi/tests/server/__init__.py b/bodhi/tests/server/__init__.py
--- a/bodhi/tests/server/__init__.py
+++ b/bodhi/tests/server/__init__.py
@@ -56,7 +56,6 @@ def create_update(session, build_nvrs, release_name=u'F17'):
         except sqlalchemy.orm.exc.NoResultFound:
             package = RpmPackage(name=name)
             session.add(package)
-            user.packages.append(package)
             testcase = TestCase(name=u'Wat')
             session.add(testcase)
             package.test_cases.append(testcase)
diff --git a/bodhi/tests/server/functional/test_users.py b/bodhi/tests/server/functional/test_users.py
--- a/bodhi/tests/server/functional/test_users.py
+++ b/bodhi/tests/server/functional/test_users.py
@@ -216,16 +216,3 @@ def test_list_users_by_nonexistant_update(self):
         self.assertEqual(body['errors'][0]['name'], 'updates')
         self.assertEqual(body['errors'][0]['description'],
                          'Invalid updates specified: carbunkle')
-
-    def test_list_users_by_package_name(self):
-        res = self.app.get('/users/', {"packages": 'bodhi'})
-        body = res.json_body
-        self.assertEqual(len(body['users']), 1)
-        self.assertEqual(body['users'][0]['name'], 'guest')
-
-    def test_list_users_by_nonexistant_package(self):
-        res = self.app.get('/users/', {"packages": 'carbunkle'}, status=400)
-        body = res.json_body
-        self.assertEqual(body['errors'][0]['name'], 'packages')
-        self.assertEqual(body['errors'][0]['description'],
-                         'Invalid packages specified: carbunkle')
diff --git a/bodhi/tests/server/test_models.py b/bodhi/tests/server/test_models.py
--- a/bodhi/tests/server/test_models.py
+++ b/bodhi/tests/server/test_models.py
@@ -92,20 +92,17 @@ class TestBodhiBase(BaseTestCase):
 
     def test__expand_with_m2m_relation(self):
         """Test the _expand() method with a many-to-many relation."""
-        p = model.Package.query.all()[0]
-        new_exclude_columns = list(model.Package.__exclude_columns__)
-        new_exclude_columns.remove('committers')
-        # p.committers is an InstrumentedList, which doesn't have an all() method. We have some
+        u = model.Update.query.all()[0]
+        # u.bugs is an InstrumentedList, which doesn't have an all() method. We have some
         # code to handle m2m relationships with all() methods, but it's not immediately obvious
         # which relationships have that for testing purposes. Thus, we can simulate this for
         # test coverage purposes by setting it to its __iter__() method.
-        p.committers.all = p.committers.__iter__
+        u.bugs.all = u.bugs.__iter__
 
-        with mock.patch.object(model.Package, '__exclude_columns__', new_exclude_columns):
-            committers = p._expand(p, p.committers, [], mock.MagicMock())
+        bugs = u._expand(u, u.bugs, [], mock.MagicMock())
 
-        self.assertEqual(len(committers), 1)
-        self.assertEqual(committers[0]['name'], 'guest')
+        self.assertEqual(len(bugs), 1)
+        self.assertEqual(bugs[0]['bug_id'], 12345)
 
     def test__expand_with_relation_in_seen(self):
         """_expand() should return the relation.id attribute if its type is in seen."""
@@ -858,11 +855,6 @@ class TestModulePackage(ModelTest, unittest.TestCase):
     klass = model.ModulePackage
     attrs = dict(name=u"TurboGears")
 
-    def do_get_dependencies(self):
-        return dict(
-            committers=[model.User(name=u'lmacken')]
-        )
-
     def setUp(self):
         super(TestModulePackage, self).setUp()
         self.package = model.ModulePackage(name=u'the-greatest-package:master')
@@ -1134,11 +1126,6 @@ def setUp(self):
         self.package = model.RpmPackage(name=u'the-greatest-package')
         self.db.add(self.package)
 
-    def do_get_dependencies(self):
-        return dict(
-            committers=[model.User(name=u'lmacken')]
-        )
-
     @mock.patch.dict(config, {'query_wiki_test_cases': True})
     def test_wiki_test_cases(self):
         """Test querying the wiki for test cases"""
@@ -1228,9 +1215,6 @@ def test_backref_second_modulebuild(self):
         # This should not raise any Exception.
         self.db.flush()
 
-    def test_committers(self):
-        self.assertEqual(self.obj.committers[0].name, u'lmacken')
-
     def test_no_builds(self):
         """Assert that one RpmBuild can be appended."""
         build = model.RpmBuild(nvr=u'the-greatest-package-1.0.0-fc17.1')
@@ -3209,22 +3193,6 @@ def test_comment_critpath_unapproved(self):
                               'additional karma from the community. Or, it must spend 14 days in '
                               'testing without any negative feedback')}])
 
-    @mock.patch('bodhi.server.mail.smtplib.SMTP')
-    @mock.patch.dict('bodhi.server.models.config',
-                     {'bodhi_email': 'bodhi@fp.o', 'smtp_server': 'smtp.fp.o'})
-    def test_comment_emails_maintainers(self, SMTP):
-        """comment() should send e-mails to the other maintainers."""
-        bowlofeggs = model.User(name=u'bowlofeggs', email=u'bowlofeggs@fp.o')
-        self.obj.builds[0].package.committers.append(bowlofeggs)
-
-        self.obj.comment(self.db, u'Here is a cool e-mail for you.', author=u'bowlofemail')
-
-        bodies = [c[1][2].decode('utf-8') for c in SMTP.return_value.sendmail.mock_calls]
-        self.assertTrue('lmacken' in bodies[0])
-        # In Python 2 this address is in the middle e-mail and in Python 3 it's in the last e-mail
-        self.assertTrue('bowlofeggs@fp.o' in '\n'.join(bodies))
-        self.assertTrue(all(['Here is a cool e-mail for you.' in b for b in bodies]))
-
     def test_comment_emails_other_commenters(self):
         """comment() should send e-mails to the other maintainers."""
         bowlofeggs = model.User(name=u'bowlofeggs', email=u'bowlofeggs@fp.o')