Example #1
0
    def update(self, id, **kwargs):
        """
        PUT /groups/{id}: Depricated!
        """
        # h.form(h.url_for('message', id=ID), method='delete')
        # Rather than delete the setting this simple blanks the required fields - or removes the config dict entry
        raise action_error(_('operation not supported'), code=501)
        group = get_group(id, is_current_persona_admin=True)

        group_dict = group.to_dict()
        group_dict.update(kwargs)
        data = {'group': group_dict, 'action': 'edit'}
        data = validate_dict(data,
                             GroupSchema(),
                             dict_to_validate_key='group',
                             template_error='groups/edit')
        group_dict = data['group']

        group.name = group_dict['name']
        #group.description                = group_dict['description'] GregM: Broke description saving, ARRGHHHHHH!!!!!!!!!
        group.default_role = group_dict['default_role']
        group.join_mode = group_dict['join_mode']
        group.member_visibility = group_dict['member_visibility']
        group.default_content_visibility = group_dict.get(
            'default_content_visibility', "public")  # Shish: hack

        # GregM: call settings_update with logo_file as avatar
        # ARRRGHHH: Hacked c.format as settings_update redirects on html
        # old_persona = c.logged_in_persona

        ## GregM DIRTIEST HACK IN HISTORY! OMFG! Works... do not try this at home!

        Session.commit()

        cformat = c.format
        cpersona = c.logged_in_persona
        c.logged_in_persona = group
        c.format = 'python'
        if 'description' in kwargs:
            settings_update(id=id, description=kwargs['description'])
        if 'avatar' in kwargs:
            settings_update(id=id, avatar=kwargs['avatar'])
        if 'website' in kwargs:
            settings_update(id=id, website=kwargs['website'])
        c.format = cformat
        c.logged_in_persona = cpersona

        Session.commit()

        user_log.info("Updated Group #%d (%s)" % (group.id, group.username))

        if c.format == 'html':
            ##return redirect(url('members', id=group.username))
            set_persona(group)

        return action_ok(message=_('group updated'), data=data)
Example #2
0
    def upgrade_request(self, **kwargs):
        if not request.POST:
            return
        schema = DynamicSchema()
        schema.chained_validators = []
        schema.fields['name'] = UnicodeString(not_empty=True)
        schema.fields['phone'] = UnicodeString(not_empty=True)
        schema.fields['email'] = Email(not_empty=True)
        if not c.logged_in_user:
            schema.fields['recaptcha_challenge_field'] = UnicodeString(
                not_empty=True)
            schema.fields['recaptcha_response_field'] = UnicodeString(
                not_empty=True)
            schema.chained_validators.append(
                ReCaptchaValidator(request.environ['REMOTE_ADDR']))

        data = {'upgrade_request': kwargs}
        data = validate_dict(data,
                             schema,
                             dict_to_validate_key='upgrade_request')

        from civicboom.lib.communication.email_lib import send_email
        form_string = ''
        for key, value in kwargs.iteritems():
            form_string += '\n%s: %s' % (key, value)

        if c.logged_in_user:
            form_string += '\nlogged_in_user: %s' % (c.logged_in_user.username)
            form_string += '\nlogged_in_persona: %s' % (
                c.logged_in_persona.username)
        else:
            form_string += '\nUser not logged in!'

        send_email(config['email.contact'],
                   subject='Civicboom',
                   content_text='upgrade account request: %s' % form_string)

        return action_ok(_('upgrade request sent'))
Example #3
0
    def update(self, id, **kwargs):
        """
        """
        if isinstance(id, PaymentAccount):
            account = id
        else:
            account = Session.query(PaymentAccount).filter(
                PaymentAccount.id == id).first()
        if not account:
            raise action_error(_('Payment account does not exist'), code=404)
        if c.logged_in_persona not in account.members:
            raise action_error(
                _('You do not have permission to view this account'), code=404)

        address_fields = PaymentAccount._address_config_order
        # Build validation schema
        schema = build_schema(
            name_type=formencode.validators.OneOf(
                ['org', 'ind'],
                messages={'missing': 'Please select a type'},
                not_empty=True),
            org_name=formencode.validators.UnicodeString(),
            ind_name=formencode.validators.UnicodeString(),
        )
        if kwargs.get('name_type') == 'org':
            schema.fields['org_name'] = formencode.validators.UnicodeString(
                not_empty=True)
        else:
            schema.fields['ind_name'] = formencode.validators.UnicodeString(
                not_empty=True)

        for address_field in address_fields:
            schema.fields[address_field] = formencode.validators.UnicodeString(
                not_empty=(address_field in PaymentAccount._address_required))

        schema.fields['address_country'] = formencode.validators.OneOf(
            country_codes.keys(),
            messages={'missing': 'Please select a country'},
            not_empty=True)
        #        if kwargs.get('address_country') in country_ec_vat:
        #            if kwargs.get('vat_no'):
        #                kwargs['vat_no'] = kwargs.get('address_country','') + kwargs['vat_no']
        #            schema.fields['vat_no'] = tax_code_validator
        #        else:
        #            schema.fields['vat_no'] = formencode.validators.Empty()
        data = {'payment': kwargs}
        data = validate_dict(data,
                             schema,
                             dict_to_validate_key='payment',
                             template_error=c.template_error if hasattr(
                                 c, 'template_error') else 'payments/edit')
        form = data['payment']

        #        if form.get('vat_no'):
        #            form['vat_no'] = form['vat_no'][2:]

        for field in account._user_edit_config:
            if form.get(field):
                account.config[field] = form[field]
            elif account.config.get(field):
                del account.config[field]

        if account.config.get('address_country') in country_ec_vat:
            if account.config['address_country'] == 'GB':
                account.taxable = True
            elif account.config.get('vat_no'):
                account.taxable = False
            else:
                account.taxable = True
        else:
            account.taxable = False

#        if form.get('ind_name'):
#            account.config['ind_name'] = form['ind_name']
#        elif account.config.get('ind_name'):
#            del account.config['ind_name']
#        if form.get('org_name'):
#            account.config['org_name'] = form['org_name']
#        elif account.config.get('org_name'):
#            del account.config['org_name']
#
#        for field_name in address_fields:
#            if form.get(field_name):
#                account.config[field_name] = form[field_name]

# account.frequency = 'month' This can be changed in the future

#return redirect(url('payment', action='show', id=payment_account.id))

        Session.commit()

        # url('payment', id=ID)
        if c.format == 'redirect':
            redirect(url('payment', id=account.id))

        return action_ok()
Example #4
0
    def new_user(self, id=None, **kwargs):
        """
        Register new user - look at exisiting user record and identify additioinal required fields to complete upload
        """
        registration_template = "/html/%s/account/register.mako" % (
            "mobile" if c.subformat == "mobile" else "web")
        # registration_template = "/html/web/account/register.mako"

        c.new_user = _get_member(id)

        # Validate User
        if c.logged_in_persona and c.logged_in_persona == c.new_user:  # from janrain login
            pass
        elif verify_email_hash(c.new_user,
                               kwargs.get('hash')):  # or from email hash
            c.logged_in_user = c.new_user  #fake logged in user for rendering template
            c.logged_in_persona = c.new_user
        else:
            raise action_error(
                code=403,
                message=
                "Unable to verify email hash - it may already have been validated?"
            )

        # Build required fields list from current user data - the template will then display these and a custom validator will be created for them
        c.required_fields = ['username', 'email', 'password', 'name', 'dob']
        if not c.logged_in_persona.id.startswith(new_user_prefix):
            c.required_fields.remove('username')
        if c.logged_in_persona.email or c.logged_in_persona.email_unverified:
            c.required_fields.remove('email')
        if len(c.logged_in_persona.login_details) > 0:
            c.required_fields.remove('password')
        if c.logged_in_persona.config["dob"] != u"":
            c.required_fields.remove('dob')

        # If no post data, display the registration form with required fields
        if request.environ['REQUEST_METHOD'] == 'GET':
            return render(registration_template)

        # Build a dynamic validation scema based on these required fields and validate the form
        schema = build_schema(*c.required_fields)
        schema.fields['terms'] = validators.NotEmpty(messages={
            'missing':
            'You must agree to the terms and conditions'
        })  # In addtion to required fields add the terms checkbox validator
        schema.fields['name'] = validators.NotEmpty(
            messages={
                'missing':
                'Please give us your full name as you wish it to appear on your profile'
            })
        schema.fields['help_type'] = formencode.validators.OneOf(
            ['org', 'ind'],
            messages={'missing':
                      'Please select a help type'})  #, if_missing='ind'
        # schema.fields['help_type'] = validators.NotEmpty(messages={'missing': 'Please select a user type'})
        data = {'register': kwargs}
        data = validate_dict(data,
                             schema,
                             dict_to_validate_key='register',
                             template_error='account/register')
        form = data['register']

        #try: # Form validation
        #    form = schema.to_python(kwargs) #dict(request.params)
        #except formencode.Invalid as error:  # If the form has errors overlay those errors over the previously rendered form
        #    form_result = error.value
        #    form_errors = error.error_dict or {}
        # htmlfill does not work with HTML5 ... bugger
        # return formencode.htmlfill.render(render(registration_template) , defaults = form_result , errors = form_errors, prefix_error=False)

        # If the validator has not forced a page render
        # then the data is fine - save the new user data
        if 'username' in form:
            c.logged_in_persona.id = form['username']
        if 'name' in form:
            c.logged_in_persona.name = form['name']
        if 'dob' in form:
            c.logged_in_persona.config['dob'] = str(form['dob'])
        if 'email' in form:
            c.logged_in_persona.email_unverified = form['email']
        if 'password' in form:
            set_password(c.logged_in_persona,
                         form['password'],
                         delay_commit=True)
        c.logged_in_persona.status = "active"
        if form['help_type'] == 'org':
            c.logged_in_persona.extra_fields['help_type'] = form['help_type']

        # AllanC - in offline demo mode ensure every user has the maximum user rights
        if config['demo_mode']:
            c.logged_in_persona.account_type = 'corp_plus'

        Session.add(
            c.logged_in_persona)  #AllanC - is this needed? Already in session?
        Session.commit()

        if c.logged_in_persona.email_unverified:
            send_verifiy_email(c.logged_in_persona)
            set_flash_message(
                _('Please check your email to validate your email address'))

        c.logged_in_persona.send_email(
            subject=_('Welcome to _site_name'),
            content_html=render(
                '/email/welcome.mako',
                extra_vars={'registered_user': c.logged_in_persona}))

        # Follow pending users from extra_fields
        for member in [
                _get_member(username)
                for username in c.logged_in_persona.extra_fields.pop(
                    'on_register_follow', '').split(',')
        ]:
            if member:
                c.logged_in_persona.follow(member, delay_commit=True)
        for member in [
                _get_member(username)
                for username in c.logged_in_persona.extra_fields.pop(
                    'on_register_follow_mutual', '').split(',')
        ]:
            if member and member.config['allow_registration_follows']:
                member.follow(c.logged_in_persona, delay_commit=True)
        Session.commit()

        # AllanC - Temp email alert for new user
        send_email(config['email.event_alert'],
                   subject='new signup',
                   content_text='%s - %s - %s - %s' %
                   (c.logged_in_persona.id, c.logged_in_persona.name,
                    c.logged_in_persona.email_normalized, form['help_type']))

        user_log.info("Registered new user")
        set_flash_message(
            _("Congratulations, you have successfully signed up to _site_name."
              ))
        # GregM: prompt_aggregate on new user :D
        ##signin_user_and_redirect(c.logged_in_persona, 'registration', prompt_aggregate=True)
        # Proto: only want to show profile introduction page if the user is registering using the desktop website
        redirection_uri = url(
            controller='misc',
            action='how_to') if c.subformat == "web" else url(
                controller='profile', action='index')
        signin_user_and_redirect(c.logged_in_persona,
                                 'registration',
                                 redirect_url=redirection_uri)
Example #5
0
    def create(self, **kwargs):
        """
        POST /groups: Create a new group

        Creates a new group with the specified username with the currently
        logged in user as as administrator of the new group

        @api groups 1.0 (WIP)
        
        @param username                   a unique username, cannot clash with existing usernames
        @param name                       display name
        @param description                description of groups purpose
        @param default_role
            admin
            editor
            contributor
            observer
        @param join_mode
            public
            invite
            invite_and_request
        @param member_visibility
            public
            private 
        @param default_content_visibility (plus account required)
            public
            private 
        
        @return 400  data invalid (ie, username that already exisits)
        @return 201  group created, data.id = new group id
        @return 301  if format redirect specifyed will redirect to show group
        """

        create_push_assignment = kwargs.get('create_push_assignment')
        if create_push_assignment:
            del kwargs['create_push_assignment']

        # url('groups') + POST
        # if only display name is specified, generate a user name
        if not kwargs.get('username') and kwargs.get("name"):
            kwargs["username"] = _gen_username(
                make_username(kwargs.get("name")))

        # if only user name is specified, generate a display name
        if not kwargs.get('name') and kwargs.get("username"):
            kwargs["name"] = kwargs.get("username")

        if not c.logged_in_persona.has_account_required('plus'):
            if not kwargs.get('member_visibility'):
                kwargs['member_visibility'] = 'public'
            if not kwargs.get('default_content_visibility'):
                kwargs['default_content_visibility'] = 'public'

        # Need to validate before creating group, not sure how we could do this via settings controller :S GregM
        data = {'settings': kwargs, 'action': 'create'}
        data = validate_dict(data,
                             CreateGroupSchema(),
                             dict_to_validate_key='settings',
                             template_error='groups/edit')
        group_dict = data['settings']

        # Create and set group admin here!
        group = Group()
        group.id = group_dict['username']
        group.name = group_dict['name']
        group.status = 'active'
        group_admin = GroupMembership()
        group_admin.member = c.logged_in_persona
        group_admin.role = "admin"
        group.members_roles.append(group_admin)
        group.payment_account = c.logged_in_persona.payment_account  # The group is allocated the same payment account as the creator. All groups are free but if they want the plus features like approval and private content then this is needed

        # GregM: Dirty hack, again... In demo mode users & groups don't have payment accounts, we need to override the account_type manually
        if config['demo_mode']:
            group.account_type = c.logged_in_persona.account_type

        #AllanC - TODO - limit number of groups a payment account can support - the could be the differnece between plus and corporate

        # GregM: Create current user as admin of group too to allow them to admin group (until permission tree is sorted!)
        #if isinstance(c.logged_in_persona, Group):
        #    group_admin_user        = GroupMembership()
        #    group_admin_user.member = c.logged_in_user
        #    group_admin_user.role   = "admin"
        #    group.members_roles.append(group_admin_user)

        Session.add(group)
        Session.commit()

        # AllanC - Hack
        # The group has been created, but the additional feilds handled by the settings controller need to be updated (e.g. description and logo image)
        # However, we have not set c.logged_in_persona so the call to the settings controller will not have the permissions for the newly created group
        # We fake the login here
        # We cant use set_persona as this called the set_persona controller action and calls a redirect
        logged_in_persona = c.logged_in_persona  # have to remeber previous persona to return to or set_persona below thinks were already swiched and will perform no action
        logged_in_persona_role = c.logged_in_persona_role
        c.logged_in_persona = group
        c.logged_in_persona_role = 'admin'

        # AllanC - old call? to be removed?
        # self.update(group.username, **kwargs) # Overlay any additional form fields over the new group object using the update method - also intercepts if format is redirect

        # Call settings controller to update group settings!
        kwargs['panel'] = 'general'

        settings_update(group, private=True, **kwargs)

        # GregM: Create new request for group (Arrgh, have to fudge the format otherwise we cause a redirect):
        format = c.format
        if create_push_assignment:
            c.format = 'python'
            assignment = create_content(
                type='assignment',
                private=False,
                title=_("Send us your stories"),
                content=
                _("Join us in making the news by telling us your stories, sending in videos, pictures or audio: Get recognition and get published - share your news with us now!"
                  ),
                format="python")
            group.config['push_assignment'] = assignment.get('data',
                                                             {}).get('id')

        c.format = format

        c.logged_in_persona = logged_in_persona
        c.logged_in_persona_role = logged_in_persona_role

        user_log.info("Created Group #%s (%s)" % (group.id, group.name))

        # AllanC - Temp email alert for new group
        send_email(config['email.event_alert'],
                   subject='new group',
                   content_text='%s - %s by %s' %
                   (c.logged_in_persona.username, c.logged_in_persona.name,
                    c.logged_in_user.username))

        # GregM: prompt_aggregate for new group :)
        set_persona(
            group,
            prompt_aggregate=True)  # Will redirect if in html or redirect mode

        return action_ok(message=_('group created'),
                         data={'id': group.id},
                         code=201)
Example #6
0
    def update(self, id='me', **kwargs):
        """
        PUT /id: Update an existing item.
        
        - Creates a custom validator schema for the input data that has changed from the DB
        - Validates the request overlaying errors if generated
        - Saves update
        - Returns written object
        """
        # Check permissions on object, find actual username and store in username
        # id will always contain me if it was passed

        private = kwargs.get('private')
        if private:
            del kwargs['private']
        #username = id
        #if not username or username == 'me':
        #    username = c.logged_in_persona.username
        #    id = 'me'
        #user_type = 'group'
        #user = get_member(username)

        user = get_member(id)

        if user.username != c.logged_in_user.username:
            raise_if_current_role_insufficent('admin', group=user)

        user_log.info("Saving general settings")

        # User panel if there else use general
        panel = kwargs['panel'] if 'panel' in kwargs and kwargs[
            'panel'] != '' else 'general'
        if 'panel' in kwargs:
            del kwargs['panel']

        # Find template from panel and user_type
        template = find_template(panel, user.__type__)

        # variables to store template and redirect urel
        panel_template = ('settings/panel/' + template).encode(
            'ascii', 'ignore')

        if c.action == 'create' and c.controller == 'groups' and c.format == 'html':
            panel_redirect = url('member', id=id)
        else:
            panel_redirect = url('setting', id=id, panel=panel)

        # If no new password set disregard (delete) all password fields!
        if kwargs.get('password_new') == '':
            del kwargs['password_new']
            try:
                del kwargs['password_current']
            except:
                pass
            try:
                del kwargs['password_new_confirm']
            except:
                pass

        # GregM: Patched to remove avatar kwarg if blank (keeping current avatar on settings save!)
        if kwargs.get('avatar') == '':
            del kwargs['avatar']

        data = build_meta(user, user.__type__, panel)

        data['settings'] = copy_user_settings(data['settings_meta'], user,
                                              user.__type__)

        data['settings'].update(kwargs)
        data['username'] = user.username

        settings = kwargs

        for delete in [
                'action', 'controller', 'sub_domain', 'format',
                '_authentication_token', 'submit', '_method'
        ]:
            try:
                del settings[delete]
            except:
                pass

        if len(settings) == 0:
            raise action_error(code=400, message=_("No settings to update"))

        # Setup custom schema for this update
        # List validators required
        validators = {}
        if len(set(settings.keys()) - set(settings_validators.keys())) > 0:
            raise action_error(
                code=400,
                message=_(
                    "You are trying to update a setting that does not exist!"))
        for validate_fieldname in [
                setting_name for setting_name in settings.keys()
                if setting_name in settings_validators and setting_name in
                kwargs and settings_base[setting_name.split('-')[0]].get(
                    'who', user.__type__) == user.__type__
        ]:
            log.debug("adding validator: %s" % validate_fieldname)
            validators[validate_fieldname] = settings_validators[
                validate_fieldname]
        # Build a dynamic validation schema based on these required fields and validate the form
        schema = build_schema(**validators)
        # Add any additional validators for custom fields
        if 'password_new' in validators:
            if 'password' in [login.type for login in user.login_details]:
                schema.fields['password_current'] = settings_validators[
                    'password_current']  # This is never added in the
            schema.chained_validators.append(
                formencode.validators.FieldsMatch('password_new',
                                                  'password_new_confirm'))
            if user.email_unverified != None:
                schema.fields[
                    'password_new'] = civicboom.lib.form_validators.base.EmptyValidator(
                    )

        validate_dict(data,
                      schema,
                      dict_to_validate_key='settings',
                      template_error=panel_template)

        # Form has passed validation - continue to save/commit changes
        settings = data['settings']

        # Save special properties that need special processing
        # (could have a dictionary of special processors here rather than having this code cludge this controller action up)
        # GregM: check kwargs as if no new avatar and has current avatar this FAILS!
        if kwargs.get('avatar') != None:
            try:
                user.avatar = process_avatar(file_obj=settings['avatar'])
            except IOError as e:
                raise action_error(
                    code=400,
                    message="Unable to read avatar image file: %s" % e)
            del settings['avatar']

        if settings.get('location_home'):
            # translation to PostGIS format is done in the validator
            user.location_home = settings.get('location_home')
            del settings['location_home']
        elif settings.get("location_home_name"):
            user.location = "SRID=4326;POINT(%f %f)" % (
                0, 0
            )  # FIXME: guess_lon_lat_from_name(request.POST["location_home_name"]), see Feature #47
            del settings['location_home_name']

        if settings.get('location_current'):
            user.location_current = settings.get('location_current')
            del settings['location_current']
        elif settings.get("location_current_name"):
            user.location = "SRID=4326;POINT(%f %f)" % (0, 0)
            del settings['location_current_name']

        if 'password_new' in settings:
            # OLD: We could put this in settings.py manager, have a dictionarys with special cases and functions to process/save them, therefor the code is transparent in the background. an idea?
            set_password(user, settings['password_new'], delay_commit=True)
            del settings['password_new']  # We dont want these saved
        if 'password_new_confirm' in settings:
            del settings['password_new_confirm']
        if 'password_current' in settings:
            del settings['password_current']

        if 'email' in settings:
            if user.email != settings['email']:
                user.email_unverified = settings['email']
                send_verifiy_email(
                    user, message=_("please verify your email address"))
            del settings['email']
            # AllanC - todo - need message to say check email

        # Save validated Sets: Needs cleaning up!
        for setting_set in [
            (setting_name, settings_base[setting_name].get('value', []))
                for setting_name in settings_base.keys()
                if settings_base[setting_name].get('type') == 'set'
        ]:
            setting_name = setting_set[0]
            # If storing using the api route_this = 'en', route_that = 'n', etc.
            if setting_name in settings:
                if setting_name in validators:
                    if hasattr(user, setting_name):
                        setattr(user, setting_name, settings[setting_name])
                    else:
                        if settings[setting_name] == None:
                            settings[setting_name] = ['']
                        user.config[setting_name] = ''.join(
                            settings[setting_name])

                    del settings[setting_name]
                    for setting_value in setting_set[1]:
                        if setting_name + '-' + setting_value in settings:
                            del settings[setting_name + '-' + setting_value]
            # If storing using the web route_this-e = 'e', route_this-n = 'n', etc.
            else:
                setting_new_value = ''
                setting_count = 0
                for setting_value in setting_set[1]:
                    if setting_name + '-' + setting_value in settings:
                        setting_count = setting_count + 1
                        setting_new_value = setting_new_value + (
                            settings[setting_name + '-' + setting_value] or '')
                        del settings[setting_name + '-' + setting_value]
                if setting_count == len(setting_set[1]):
                    if validators.get(setting_name + '-' + setting_value):
                        if hasattr(user, setting_name):
                            setattr(user, setting_name, setting_new_value)
                        else:
                            user.config[setting_name] = setting_new_value

        # Save all remaining validated(!) properties
        for setting_name in settings.keys():
            log.debug("saving setting %s" % setting_name)
            if setting_name in validators:
                if hasattr(user, setting_name):
                    setattr(user, setting_name, settings[setting_name])
                else:
                    if settings[setting_name] == None:
                        try:
                            del user.config[setting_name]
                        except:
                            pass
                    else:
                        user.config[setting_name] = settings[setting_name]

        Session.commit()

        if private:
            return

        if c.format == 'html':
            set_flash_message(action_ok(_('Settings updated')))
            return redirect(url(panel_redirect))

        data['settings'] = copy_user_settings(
            data['settings_meta'], user, user.__type__
        )  # AllanC - a hack to convert the settings dictionary to a string dict because they had object types in it that could not be converted to JSON

        return action_ok(
            message=_('Settings updated'),
            data=data,
            panel=panel,
            template=panel_template,
        )
Example #7
0
    def create(self, **kwargs):
        """
        POST /messages: Create a new item.
        
        @api messages 1.0 (WIP)
        
        @param target   the username of the target user (can be comma separated list)
        @param subject  message subject
        @param content  message body
        
        @return 201   message sent
                id    list of message id's created
        @return 400   
        @return invalid missing required field
        
        @comment Shish  do we want some sort of "too many messages, stop spamming" response?
        @comment AllanC yes we do, to be implemented - raised on Redmine #474
        """
        # url('messages')

        # Validation needs to be overlayed oved a data dictonary, so we wrap kwargs in the data dic - will raise invalid if needed
        data = {'message': kwargs}
        data = validate_dict(data,
                             new_message_schema,
                             dict_to_validate_key='message',
                             template_error='messages/new')
        kwargs = data['message']

        #member_to = get_member(kwargs.get('target'), set_html_action_fallback=True)

        messages_sent = []

        # Construct special message (rather than using a prefab from messages.'message_name')
        message = dict(
            name='message',
            default_route='e',
            source=c.logged_in_persona.username,
            target=kwargs.get('target'),
            subject=kwargs.get('subject'),
            content=kwargs.get('content'),
        )

        members = get_members(kwargs.get('target'), expand_group_members=False)
        for member in members:
            m = Message()
            m.target = member
            m.source = c.logged_in_persona
            m.subject = message['subject']
            m.content = message['content']
            Session.add(m)
            messages_sent.append(m)

        Session.commit()

        # Alert via email, MSN, etc - NOTE the message route for message_recived does not generate a notification by default
        #messages.send_notification(members, message)
        # AllanC - DANG!! cant do a batch one here ... while this is efficent, we cant get the id of the message to the email - this is needed to allow replys to the message
        #          not really send_notification ... but this is used to trigger the 'email' route for the message
        for message in messages_sent:
            messages.send_notification(message.target, message)

        #user_log.debug("Sending message to User #%d (%s)" % (target.id, target.username))
        user_log.debug("Sent message to %s" % kwargs.get('target'))

        if not messages_sent:
            raise action_error(_("Message failed to send"), code=400)

        return action_ok(_("Message sent"),
                         code=201,
                         data={'id':
                               [m.id
                                for m in messages_sent]})  #data={'id': m.id}