Invite-only groups in FAS

Jason L Tibbitts III tibbs at math.uh.edu
Sat Jan 23 06:21:32 UTC 2010


I'm not sure if there's a patch review procedure for FAS, so rather than
dumping this in a ticket I've elected to post it here.  If a ticket
would be better, please let me know.

After wishing for some time that we could make the packager group closed
to applications by users, I went ahead and (with help from Ricky and
Toshio) set up a local FAS instance and hacked up the following patch.
All it does is provide an additional group property, invite_only, with
the necessary interface to view and change it, along with a help
string.  When set, the "Apply" links and buttons are hidden.

This is just a first-pass hack from someone who doesn't know what
they're doing.  It might not be enough to just hide the links, but it's
a start.  I'm not really satisfied with how long the extra conditionals
make some of the bits in the templates, and I'm not sure it's good
interface policy to leave the position in the group list completely
blank when the user is not a member and the group is invite only.  (I
don't know how to do nested conditionals in genshi, if that's even
possible, so....)

I included a change to the README file which would have smoothed the
setup process a bit for me, but I did not delete the sqlalchemy section.
An error in the fas2.sql file was also fixed (an index was being created
on a column that didn't exist).

Suggestions welcome.  Commit if you like.  None of this warrants
copyright on any of the files.  I removed my local fas.cfg changes so
the diffstat is wrong.

 - J<

 README                           |    4 ++--
 fas.cfg                          |   14 +++++++-------
 fas/group.py                     |    5 +++--
 fas/help.py                      |    1 +
 fas/templates/group/edit.html    |    6 ++++++
 fas/templates/group/list.html    |    4 ++--
 fas/templates/group/members.html |    2 +-
 fas/templates/group/view.html    |    6 +++++-
 fas2.sql                         |    2 +-
 9 files changed, 28 insertions(+), 16 deletions(-)

diff --git a/README b/README
index db648e7..da61da3 100644
--- a/README
+++ b/README
@@ -82,14 +82,14 @@ Before you can get started, make sure to have the following packages installed
   yum install git-core postgresql-plpython postgresql-server postgresql-python \
   python-TurboMail TurboGears pygpgme python-sqlalchemy python-genshi \
   python-psycopg2 pytz python-babel babel python-GeoIP python-openid \
-  python-fedora python-migrate python-memcached python-tgcaptcha
+  python-fedora python-migrate python-memcached python-tgcaptcha pyOpenSSL gettext
 
   # Note: on RHEL5 you need postgresql-pl instead of postgresql-plpython
 
   yum install git-core postgresql-pl postgresql-server postgresql-python \
   python-TurboMail TurboGears pygpgme python-sqlalchemy python-genshi \
   python-psycopg2 pytz python-babel babel python-GeoIP python-openid \
-  python-fedora python-migrate python-memcached python-tgcaptcha
+  python-fedora python-migrate python-memcached python-tgcaptcha pyOpenSSL gettext
 
 At present, the database needs to be a postgres database since we use triggers
 to manage some of the data (like syncing accounts with bugzilla).
diff --git a/fas/group.py b/fas/group.py
index cda2265..6c6cd52 100644
--- a/fas/group.py
+++ b/fas/group.py
@@ -290,8 +290,8 @@ class Group(controllers.Controller):
     @expose(template="fas.templates.group.edit")
     def save(self, groupname, display_name, owner, group_type, 
              needs_sponsor=0, user_can_remove=1, prerequisite='', 
-             url='', mailing_list='', mailing_list_url='', irc_channel='', 
-             irc_network='', joinmsg='', apply_rules="None"):
+             url='', mailing_list='', mailing_list_url='', invite_only=0,
+             irc_channel='', irc_network='', joinmsg='', apply_rules="None"):
         '''Edit a group'''
         username = turbogears.identity.current.user_name
         person = People.by_username(username)
@@ -316,6 +316,7 @@ class Group(controllers.Controller):
                 group.url = url
                 group.mailing_list = mailing_list
                 group.mailing_list_url = mailing_list_url
+                group.invite_only = invite_only
                 group.irc_channel = irc_channel
                 group.irc_network = irc_network
                 group.joinmsg = joinmsg
diff --git a/fas/help.py b/fas/help.py
index 41acde2..09b88c1 100644
--- a/fas/help.py
+++ b/fas/help.py
@@ -54,6 +54,7 @@ class Help(controllers.Controller):
             'group_url':            [_('Group URL (Optional)'), _('''<p>A URL or wiki page for the group (for example, <a href="https://fedoraproject.org/wiki/Infrastructure">https://fedoraproject.org/wiki/Infrastructure</a>).</p>''')],
             'group_mailing_list':     [_('Group Mailing List (Optional)'), _('''<p>A mailing list for the group (for example, fedora-infrastructure-list at redhat.com).</p>''')],
             'group_mailing_list_url': [_('Group Mailing List URL (Optional)'), _('''<p>A URL for the group's mailing list (for example, <a href="http://www.redhat.com/mailman/listinfo/fedora-infrastructure-list">http://www.redhat.com/mailman/listinfo/fedora-infrastructure-list</a>).</p>''')],
+            'group_invite_only': [_('Invite Only'), _('''<p>If users should not normally be able to apply to the group, setting this will hide the usual "Apply!" links and buttons.  Users can still be added to a group directly by an admin or sponsor.</p>''')],
             'group_irc_channel': [_('Group IRC Channel (Optional)'), _('''<p>An IRC channel for the group (for example, #fedora-admin).</p>''')],
             'group_irc_network': [_('Group IRC Network (Optional)'), _('''<p>The IRC Network for the group's IRC channel (for example, Freenode).</p>''')],
             'group_needs_sponsor':  [_('Needs Sponsor'), _('''<p>If your group requires sponsorship (recommended), this means that when a user is approved by a sponsor.  That relationship is recorded in the account system.  If user A sponsors user N, then in viewing the members of this group, people will know to contact user A about user N if something goes wrong.  If this box is unchecked, this means that only approval is needed and no relationship is recorded about who did the approving</p>''')],
diff --git a/fas/templates/group/edit.html b/fas/templates/group/edit.html
index 2ab9b33..2dcbcc4 100644
--- a/fas/templates/group/edit.html
+++ b/fas/templates/group/edit.html
@@ -50,6 +50,12 @@
         <script type="text/javascript">var group_owner = new HelpBalloon({dataURL: '${tg.url('/help/get_help/group_owner')}'});</script>
       </div>
       <div class="field">
+        <label for="invite_only">${_('Invite Only:')}</label>
+        <input py:if="group.invite_only" type="checkbox" id="invite_only" name="invite_only" value="1" checked="checked" />
+        <input py:if="not group.invite_only" type="checkbox" id="invite_only" name="invite_only" value="1" />
+        <script type="text/javascript">var group_invite_only = new HelpBalloon({dataURL: '${tg.url('/help/get_help/group_invite_only')}'});</script>
+      </div>
+      <div class="field">
         <label for="needs_sponsor">${_('Needs Sponsor:')}</label>
         <input py:if="group.needs_sponsor" type="checkbox" id="needs_sponsor" name="needs_sponsor" value="1" checked="checked" />
         <input py:if="not group.needs_sponsor" type="checkbox" id="needs_sponsor" name="needs_sponsor" value="1" />
diff --git a/fas/templates/group/list.html b/fas/templates/group/list.html
index 9c45055..99f1e18 100644
--- a/fas/templates/group/list.html
+++ b/fas/templates/group/list.html
@@ -40,8 +40,8 @@
               <span class="approved" py:if="group in person.approved_memberships">${_('Approved')}</span>
               <span class="unapproved" py:if="group in person.unapproved_memberships">${_('Unapproved')}</span>
             </a>
-            <a py:if="group not in person.memberships" href="${tg.url('/group/application_screen/%s/%s' % (group.name, person.username))}"><span>${_('Apply')}</span></a>
-            <script py:if="group not in person.memberships" type="text/javascript">var hb1 = new HelpBalloon({dataURL: '${tg.url('/help/get_help/group_apply')}'});</script>
+            <a py:if="not group.invite_only and group not in person.memberships" href="${tg.url('/group/application_screen/%s/%s' % (group.name, person.username))}"><span>${_('Apply')}</span></a>
+            <script py:if="not group.invite_only and group not in person.memberships" type="text/javascript">var hb1 = new HelpBalloon({dataURL: '${tg.url('/help/get_help/group_apply')}'});</script>
           </td>
         </tr>
       </tbody>
diff --git a/fas/templates/group/members.html b/fas/templates/group/members.html
index 0339ebb..d13b41d 100644
--- a/fas/templates/group/members.html
+++ b/fas/templates/group/members.html
@@ -25,7 +25,7 @@
       <span py:if="group in person.memberships and not group in person.approved_memberships" class="unapproved">${_('Unapproved')}</span>
       <span py:if="not group in person.memberships">${_('Not a Member')}</span>
     </h3>
-    <form py:if="not group in person.memberships" action="${tg.url('/group/application_screen/%s/%s' % (group.name, person.username))}">
+    <form py:if="not group.invite_only and not group in person.memberships" action="${tg.url('/group/application_screen/%s/%s' % (group.name, person.username))}">
       <div>
         <!--<input type="text" name="requestField" value="${_('Please let me join...')}" />-->
         <input type="submit" value="${('Apply!')}" />
diff --git a/fas/templates/group/view.html b/fas/templates/group/view.html
index 62f7f22..17629af 100644
--- a/fas/templates/group/view.html
+++ b/fas/templates/group/view.html
@@ -23,7 +23,7 @@
       <span py:if="group in person.memberships and not group in person.approved_memberships" class="unapproved">${_('Unapproved')}</span>
       <span py:if="not group in person.memberships">${_('Not a Member')}</span>
     </h3>
-    <form py:if="not group in person.memberships" action="${tg.url('/group/application_screen/%s/%s' % (group.name, person.username))}">
+    <form py:if="not group.invite_only and not group in person.memberships" action="${tg.url('/group/application_screen/%s/%s' % (group.name, person.username))}">
       <div>
         <!--<input type="text" name="requestField" value="${_('Please let me join...')}" />-->
         <input type="submit" value="${('Apply!')}" />
@@ -61,6 +61,10 @@
         </dd>
         </py:if>
 
+        <dt>${_('Invite Only:')}</dt><dd>
+        <py:if test="group.invite_only">${_('Yes')}</py:if>
+        <py:if test="not group.invite_only">${_('No')}</py:if>
+        &nbsp;</dd>
         <dt>${_('Needs Sponsor:')}</dt><dd>
         <py:if test="group.needs_sponsor">${_('Yes')}</py:if>
         <py:if test="not group.needs_sponsor">${_('No')}</py:if>
diff --git a/fas2.sql b/fas2.sql
index c91cb19..d55be60 100644
--- a/fas2.sql
+++ b/fas2.sql
@@ -110,6 +110,7 @@ CREATE TABLE groups (
     group_type VARCHAR(16),
     needs_sponsor BOOLEAN DEFAULT FALSE,
     user_can_remove BOOLEAN DEFAULT TRUE,
+    invite_only BOOLEAN DEFAULT FALSE,
     prerequisite_id INTEGER REFERENCES groups(id),
     joinmsg TEXT NULL DEFAULT '',
     apply_rules TEXT,
@@ -120,7 +121,6 @@ CREATE TABLE groups (
 );
 
 create index groups_group_type_idx on groups(group_type);
-create index groups_email_idx on groups(email);
 cluster groups_group_type_idx on groups;
 
 CREATE TABLE person_roles (


More information about the infrastructure mailing list