fedora-infrastructure/fas/fas/group.py

522 lines
22 KiB
Python

import turbogears
from turbogears import controllers, expose, paginate, identity, redirect, widgets, validate, validators, error_handler
from turbogears.database import session
import cherrypy
import fas
from fas.auth import *
from fas.user import KnownUser
import re
import turbomail
class KnownGroup(validators.FancyValidator):
'''Make sure that a group already exists'''
def _to_python(self, value, state):
return value.strip()
def validate_python(self, value, state):
try:
g = Groups.by_name(value)
except InvalidRequestError:
raise validators.Invalid(_("The group '%s' does not exist.") % value, value, state)
class UnknownGroup(validators.FancyValidator):
'''Make sure that a group doesn't already exist'''
def _to_python(self, value, state):
return value.strip()
def validate_python(self, value, state):
try:
g = Groups.by_name(value)
except InvalidRequestError:
pass
else:
raise validators.Invalid(_("The group '%s' already exists.") % value, value, state)
class GroupCreate(validators.Schema):
name = validators.All(
UnknownGroup,
validators.String(max=32, min=3),
validators.Regex(regex='^[a-z0-9\-]+$'),
)
display_name = validators.NotEmpty
owner = validators.All(
KnownUser,
validators.NotEmpty,
)
prerequisite = KnownGroup
#group_type = something
class GroupSave(validators.Schema):
groupname = validators.All(KnownGroup, validators.String(max=32, min=3))
display_name = validators.NotEmpty
owner = KnownUser
prerequisite = KnownGroup
#group_type = something
class GroupApply(validators.Schema):
groupname = KnownGroup
targetname = KnownUser
class GroupSponsor(validators.Schema):
groupname = KnownGroup
targetname = KnownUser
class GroupRemove(validators.Schema):
groupname = KnownGroup
targetname = KnownUser
class GroupUpgrade(validators.Schema):
groupname = KnownGroup
targetname = KnownUser
class GroupDowngrade(validators.Schema):
groupname = KnownGroup
targetname = KnownUser
class GroupView(validators.Schema):
groupname = KnownGroup
class GroupEdit(validators.Schema):
groupname = KnownGroup
class GroupDump(validators.Schema):
groupname = KnownGroup
class GroupInvite(validators.Schema):
groupname = KnownGroup
class GroupSendInvite(validators.Schema):
groupname = KnownGroup
target = validators.Email(not_empty=True, strip=True),
#class findUser(widgets.WidgetsList):
# username = widgets.AutoCompleteField(label=_('Username'), search_controller='search', search_param='username', result_name='people')
# action = widgets.HiddenField(default='apply', validator=validators.String(not_empty=True))
# groupname = widgets.HiddenField(validator=validators.String(not_empty=True))
#
#findUserForm = widgets.ListForm(fields=findUser(), submit_text=_('Invite'))
class Group(controllers.Controller):
def __init__(self):
'''Create a Group Controller.'''
@identity.require(turbogears.identity.not_anonymous())
def index(self):
'''Perhaps show a nice explanatory message about groups here?'''
return dict()
def jsonRequest(self):
return 'tg_format' in cherrypy.request.params and \
cherrypy.request.params['tg_format'] == 'json'
@expose(template="fas.templates.error")
def error(self, tg_errors=None):
'''Show a friendly error message'''
if not tg_errors:
turbogears.redirect('/')
return dict(tg_errors=tg_errors)
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupView())
@error_handler(error)
@expose(template="fas.templates.group.view")
def view(self, groupname):
'''View group'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
group = Groups.by_name(groupname)
if not canViewGroup(person, group):
turbogears.flash(_("You cannot view '%s'") % group.name)
turbogears.redirect('/group/list')
return dict()
else:
return dict(group=group)
@identity.require(turbogears.identity.not_anonymous())
@expose(template="fas.templates.group.new")
def new(self):
'''Display create group form'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
if not canCreateGroup(person, Groups.by_name(config.get('admingroup'))):
turbogears.flash(_('Only FAS adminstrators can create groups.'))
turbogears.redirect('/')
return dict()
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupCreate())
@error_handler(error)
@expose(template="fas.templates.group.new")
def create(self, name, display_name, owner, group_type, needs_sponsor=0, user_can_remove=1, prerequisite='', joinmsg=''):
'''Create a group'''
groupname = name
person = People.by_username(turbogears.identity.current.user_name)
person_owner = People.by_username(owner)
if not canCreateGroup(person, Groups.by_name(config.get('admingroup'))):
turbogears.flash(_('Only FAS adminstrators can create groups.'))
turbogears.redirect('/')
try:
owner = People.by_username(owner)
group = Groups()
group.name = name
group.display_name = display_name
group.owner_id = person_owner.id
group.group_type = group_type
group.needs_sponsor = bool(needs_sponsor)
group.user_can_remove = bool(user_can_remove)
if prerequisite:
prerequisite = Groups.by_name(prerequisite)
group.prerequisite = prerequisite
group.joinmsg = joinmsg
# Log here
session.flush()
except TypeError:
turbogears.flash(_("The group: '%s' could not be created.") % groupname)
return dict()
else:
try:
owner.apply(group, person) # Apply...
session.flush()
owner.sponsor(group, person)
owner.upgrade(group, person)
owner.upgrade(group, person)
except KeyError:
turbogears.flash(_("The group: '%(group)s' has been created, but '%(user)s' could not be added as a group administrator.") % {'group': group.name, 'user': owner.username})
else:
turbogears.flash(_("The group: '%s' has been created.") % group.name)
turbogears.redirect('/group/view/%s' % group.name)
return dict()
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupEdit())
@error_handler(error)
@expose(template="fas.templates.group.edit")
def edit(self, groupname):
'''Display edit group form'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
group = Groups.by_name(groupname)
if not canAdminGroup(person, group):
turbogears.flash(_("You cannot edit '%s'.") % group.name)
turbogears.redirect('/group/view/%s' % group.name)
return dict(group=group)
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupSave())
@error_handler(error)
@expose(template="fas.templates.group.edit")
def save(self, groupname, display_name, owner, group_type, needs_sponsor=0, user_can_remove=1, prerequisite='', joinmsg=''):
'''Edit a group'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
group = Groups.by_name(groupname)
if not canEditGroup(person, group):
turbogears.flash(_("You cannot edit '%s'.") % group.name)
turbogears.redirect('/group/view/%s' % group.name)
else:
try:
owner = People.by_username(owner)
group.display_name = display_name
group.owner = owner
group.group_type = group_type
group.needs_sponsor = bool(needs_sponsor)
group.user_can_remove = bool(user_can_remove)
if prerequisite:
prerequisite = Groups.by_name(prerequisite)
group.prerequisite = prerequisite
group.joinmsg = joinmsg
# Log here
session.flush()
except:
turbogears.flash(_('The group details could not be saved.'))
else:
turbogears.flash(_('The group details have been saved.'))
turbogears.redirect('/group/view/%s' % group.name)
return dict(group=group)
@identity.require(turbogears.identity.not_anonymous())
@expose(template="fas.templates.group.list", allow_json=True)
def list(self, search='*'):
username = turbogears.identity.current.user_name
person = People.by_username(username)
memberships = {}
groups = []
re_search = re.sub(r'\*', r'%', search).lower()
results = Groups.query.filter(Groups.name.like(re_search)).order_by('name').all()
for group in results:
if canViewGroup(person, group):
groups.append(group)
memberships[group.name] = group.approved_roles
if not len(groups):
turbogears.flash(_("No Groups found matching '%s'") % search)
return dict(groups=groups, search=search, memberships=memberships)
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupApply())
@error_handler(error)
@expose(template='fas.templates.group.view')
def apply(self, groupname, targetname=None):
'''Apply to a group'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
if not targetname:
target = person
else:
target = People.by_username(targetname)
group = Groups.by_name(groupname)
if not canApplyGroup(person, group, target):
# turbogears.flash(_('%(user)s could not apply to %(group)s.') % \
# {'user': target.username, 'group': group.name })
turbogears.redirect('/group/view/%s' % group.name)
return dict()
else:
try:
target.apply(group, person)
except fas.ApplyError, e:
turbogears.flash(_('%(user)s could not apply to %(group)s: %(error)s') % \
{'user': target.username, 'group': group.name, 'error': e})
turbogears.redirect('/group/view/%s' % group.name)
else:
# TODO: How do we handle gettext calls for these kinds of emails?
# TODO: CC to right place, put a bit more thought into how to most elegantly do this
# TODO: Maybe that @fedoraproject.org (and even -sponsors) should be configurable somewhere?
message = turbomail.Message(config.get('accounts_email'), '%(group)s-sponsors@%(host)s' % {'group': group.name, 'host': config.get('email_host')}, \
"Fedora '%(group)s' sponsor needed for %(user)s" % {'user': target.username, 'group': group.name})
url = config.get('base_url_filter.base_url') + turbogears.url('/group/edit/%s' % groupname)
message.plain = _('''
Fedora user %(user)s, aka %(name)s <%(email)s> has requested
membership for %(applicant)s (%(applicant_name)s) in the %(group)s group and needs a sponsor.
Please go to %(url)s to take action.
''') % {'user': person.username, 'name': person.human_name, 'applicant': target.username, 'applicant_name': target.human_name, 'email': person.emails['primary'], 'url': url, 'group': group.name}
turbomail.enqueue(message)
turbogears.flash(_('%(user)s has applied to %(group)s!') % \
{'user': target.username, 'group': group.name})
turbogears.redirect('/group/view/%s' % group.name)
return dict()
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupSponsor())
@error_handler(error)
@expose(template='fas.templates.group.view')
def sponsor(self, groupname, targetname):
'''Sponsor user'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
target = People.by_username(targetname)
group = Groups.by_name(groupname)
if not canSponsorUser(person, group, target):
turbogears.flash(_("You cannot sponsor '%s'") % target.username)
turbogears.redirect('/group/view/%s' % group.name)
return dict()
else:
try:
target.sponsor(group, person)
except fas.SponsorError, e:
turbogears.flash(_("%(user)s could not be sponsored in %(group)s: %(error)s") % \
{'user': target.username, 'group': group.name, 'error': e})
turbogears.redirect('/group/view/%s' % group.name)
else:
import turbomail
message = turbomail.Message(config.get('accounts_email'), target.emails['primary'], "Your Fedora '%s' membership has been sponsored" % group.name)
message.plain = _('''
%(name)s <%(email)s> has sponsored you for membership in the %(group)s
group of the Fedora account system. If applicable, this change should
propagate into the e-mail aliases and CVS repository within an hour.
%(joinmsg)s
''') % {'group': group.name, 'name': person.human_name, 'email': person.emails['primary'], 'joinmsg': group.joinmsg}
turbomail.enqueue(message)
turbogears.flash(_("'%s' has been sponsored!") % target.human_name)
turbogears.redirect('/group/view/%s' % group.name)
return dict()
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupRemove())
@error_handler(error)
@expose(template='fas.templates.group.view')
def remove(self, groupname, targetname):
'''Remove user from group'''
# TODO: Add confirmation?
username = turbogears.identity.current.user_name
person = People.by_username(username)
target = People.by_username(targetname)
group = Groups.by_name(groupname)
if not canRemoveUser(person, group, target):
turbogears.flash(_("You cannot remove '%(user)s' from '%(group)s'.") % {'user': target.username, 'group': group.name})
turbogears.redirect('/group/view/%s' % group.name)
return dict()
else:
try:
target.remove(group, target)
except fas.RemoveError, e:
turbogears.flash(_("%(user)s could not be removed from %(group)s: %(error)s") % \
{'user': target.username, 'group': group.name, 'error': e})
turbogears.redirect('/group/view/%s' % group.name)
else:
message = turbomail.Message(config.get('accounts_email'), target.emails['primary'], "Your Fedora '%s' membership has been removed" % group.name)
message.plain = _('''
%(name)s <%(email)s> has removed you from the '%(group)s'
group of the Fedora Accounts System This change is effective
immediately for new operations, and should propagate into the e-mail
aliases within an hour.
''') % {'group': group.name, 'name': person.human_name, 'email': person.emails['primary']}
turbomail.enqueue(message)
turbogears.flash(_('%(name)s has been removed from %(group)s') % \
{'name': target.username, 'group': group.name})
turbogears.redirect('/group/view/%s' % group.name)
return dict()
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupUpgrade())
@error_handler(error)
@expose(template='fas.templates.group.view')
def upgrade(self, groupname, targetname):
'''Upgrade user in group'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
target = People.by_username(targetname)
group = Groups.by_name(groupname)
if not canUpgradeUser(person, group, target):
turbogears.flash(_("You cannot upgrade '%s'") % target.username)
turbogears.redirect('/group/view/%s' % group.name)
return dict()
else:
try:
target.upgrade(group, person)
except fas.UpgradeError, e:
turbogears.flash(_('%(name)s could not be upgraded in %(group)s: %(error)s') % \
{'name': target.username, 'group': group.name, 'error': e})
turbogears.redirect('/group/view/%s' % group.name)
else:
import turbomail
message = turbomail.Message(config.get('accounts_email'), target.emails['primary'], "Your Fedora '%s' membership has been upgraded" % group.name)
# Should we make person.upgrade return this?
role = PersonRoles.query.filter_by(group=group, member=target).one()
status = role.role_type
message.plain = _('''
%(name)s <%(email)s> has upgraded you to %(status)s status in the
'%(group)s' group of the Fedora Accounts System This change is
effective immediately for new operations, and should propagate
into the e-mail aliases within an hour.
''') % {'group': group.name, 'name': person.human_name, 'email': person.emails['primary'], 'status': status}
turbomail.enqueue(message)
turbogears.flash(_('%s has been upgraded!') % target.username)
turbogears.redirect('/group/view/%s' % group.name)
return dict()
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupDowngrade())
@error_handler(error)
@expose(template='fas.templates.group.view')
def downgrade(self, groupname, targetname):
'''Upgrade user in group'''
username = turbogears.identity.current.user_name
person = People.by_username(username)
target = People.by_username(targetname)
group = Groups.by_name(groupname)
if not canDowngradeUser(person, group, target):
turbogears.flash(_("You cannot downgrade '%s'") % target.username)
turbogears.redirect('/group/view/%s' % group.name)
return dict()
else:
try:
target.downgrade(group, person)
except fas.DowngradeError, e:
turbogears.flash(_('%(name)s could not be downgraded in %(group)s: %(error)s') % \
{'name': target.username, 'group': group.name, 'error': e})
turbogears.redirect('/group/view/%s' % group.name)
else:
import turbomail
message = turbomail.Message(config.get('accounts_email'), target.emails['primary'], "Your Fedora '%s' membership has been downgraded" % group.name)
role = PersonRoles.query.filter_by(group=group, member=target).one()
status = role.role_type
message.plain = _('''
%(name)s <%(email)s> has downgraded you to %(status)s status in the
'%(group)s' group of the Fedora Accounts System This change is
effective immediately for new operations, and should propagate
into the e-mail aliases within an hour.
''') % {'group': group.name, 'name': person.human_name, 'email': person.emails['primary'], 'status': status}
turbomail.enqueue(message)
turbogears.flash(_('%s has been downgraded!') % target.username)
turbogears.redirect('/group/view/%s' % group.name)
return dict()
@identity.require(turbogears.identity.not_anonymous())
@validate(validators=GroupDump())
@error_handler(error)
@expose(template="genshi-text:fas.templates.group.dump", format="text", content_type='text/plain; charset=utf-8')
def dump(self, groupname):
username = turbogears.identity.current.user_name
person = People.by_username(username)
group = Groups.by_name(groupname)
if not canViewGroup(person, group):
turbogears.flash(_("You cannot view '%s'") % group.name)
turbogears.redirect('/group/list')
return dict()
else:
return dict(group=group)
@identity.require(identity.not_anonymous())
@validate(validators=GroupInvite())
@error_handler(error)
@expose(template='fas.templates.group.invite')
def invite(self, groupname):
username = turbogears.identity.current.user_name
person = People.by_username(username)
group = Groups.by_name(groupname)
return dict(person=person, group=group)
@identity.require(identity.not_anonymous())
@validate(validators=GroupSendInvite())
@error_handler(error)
@expose(template='fas.templates.group.invite')
def sendinvite(self, groupname, target):
import turbomail
username = turbogears.identity.current.user_name
person = People.by_username(username)
group = Groups.by_name(groupname)
if isApproved(person, group):
message = turbomail.Message(person.emails['primary'], target, _('Come join The Fedora Project!'))
message.plain = _('''
%(name)s <%(email)s> has invited you to join the Fedora
Project! We are a community of users and developers who produce a
complete operating system from entirely free and open source software
(FOSS). %(name)s thinks that you have knowledge and skills
that make you a great fit for the Fedora community, and that you might
be interested in contributing.
How could you team up with the Fedora community to use and develop your
skills? Check out http://fedoraproject.org/join-fedora for some ideas.
Our community is more than just software developers -- we also have a
place for you whether you're an artist, a web site builder, a writer, or
a people person. You'll grow and learn as you work on a team with other
very smart and talented people.
Fedora and FOSS are changing the world -- come be a part of it!''') % {'name': person.human_name, 'email': person.emails['primary']}
turbomail.enqueue(message)
turbogears.flash(_('Message sent to: %s') % target)
turbogears.redirect('/group/view/%s' % group.name)
else:
turbogears.flash(_("You are not in the '%s' group.") % group.name)
return dict(target=target, person=person, group=group)