예제 #1
0
def approve_new_user():
    """
    Approves a new user from the approve new members form
    """

    # retrieve the user id from the page arguments passed by the button
    user_id = request.args(0)
    user = db.auth_user[user_id]

    template_dict = {
        'name':
        user.first_name,
        'user_url':
        URL('people', 'user', args=[user_id], scheme=True, host=True),
        'profile_url':
        URL('user', 'profile', scheme=True, host=True),
        'h_and_s_url':
        URL('health_safety', 'health_and_safety', scheme=True, host=True),
        'admin':
        auth.user.first_name + " " + auth.user.last_name
    }

    safe_mailer(to=user.email,
                subject='SAFE: registration approved',
                template='registration_approved.html',
                template_dict=template_dict)

    # update the registration key for that user ID to remove 'pending'
    db(db.auth_user.id == user_id).update(registration_key='')

    session.flash = CENTER(B('User approved.'), _style='color: green')

    redirect(URL('people', 'administer_new_users'))

    return
예제 #2
0
def reject_new_user():
    """
    Rejects a new user from the approve new members form
    """

    # retrieve the user id from the page arguments passed by the button
    user_id = request.args(0)
    user = db.auth_user[user_id]

    template_dict = {
        'name': user.first_name,
        'admin': auth.user.first_name + " " + auth.user.last_name
    }

    safe_mailer(to=user.email,
                subject='SAFE: registration rejected',
                template='registration_rejected.html',
                template_dict=template_dict)

    # remove that row from the auth_user database
    db(db.auth_user.id == user_id).delete()

    session.flash = CENTER(B('User rejected.'), _style='color: green')

    redirect(URL('people', 'administer_new_users'))

    return
예제 #3
0
def send_weekly_summary():
    """
    Sends out an email to selected staff attaching a text representation
    of the upcoming research visit schedule and a health and safety 
    summary of who is going to be on site.
    """

    db = current.db

    # Recipients - anybody who is a member of the weekly_summary group

    others = db((db.auth_user.id == db.auth_membership.user_id)
                & (db.auth_group.id == db.auth_membership.group_id)
                & (db.auth_group.role == 'weekly_summary')).select(
                    db.auth_user.email, db.auth_user.alternative_email)

    # Build the recipient list
    send_to = []

    for recip in others:
        send_to.append(recip.alternative_email)
        send_to.append(recip.email)

    # clear out blanks
    send_to = [eml for eml in send_to if eml is not None and eml != '']

    # get the file contents
    try:
        now = datetime.date.today().isoformat()
        attach = {
            'SAFE_visits_{}.txt'.format(now): all_rv_summary_text(),
            'Visitor_H_and_S_info_{}.pdf'.format(now):
            health_and_safety_report()
        }

        safe_mailer(subject='Weekly research visit summary',
                    to=send_to,
                    template='weekly_rv_summary.html',
                    template_dict=dict(),
                    attachment_string_objects=attach)

        # commit changes to the db - necessary for things running from models
        db.commit()

        return 'Weekly research visit summary emailed'
    except BaseException:
        raise RuntimeError('Failed to email weekly research visit summary')
예제 #4
0
def resend_email():
    """
    Resends the email with the given id
    """

    mail_id = request.vars['mail_id']

    rec = db.safe_web_email_log[mail_id]

    # populate the emailer function with the db contents,
    # recovering the dict from json and turning off the cc_info
    # since this decision was made on the original send and so
    # cc is already populated.

    # also need to extract multiple emails from formatted strings:
    # '|xxx@yyy|xxx@yyy|xxx@yyy\'

    def string_to_list(item):

        return [ml for ml in item.split('|')
                if ml != ''] if item is not None else None

    to = string_to_list(rec.email_to)
    cc = string_to_list(rec.email_cc)
    bcc = string_to_list(rec.email_bcc)

    status = safe_mailer(subject=rec.subject,
                         template=rec.template,
                         template_dict=simplejson.loads(rec.template_dict),
                         to=to,
                         cc=cc,
                         cc_info=False,
                         bcc=bcc,
                         reply_to=rec.reply_to)

    status = 'sent' if status else 'failed'
    rec.update_record(status=status)

    session.flash = 'Attempted to resend email: ' + status
    redirect(URL('scheduler', 'email_failures'))

    return 'resent'
예제 #5
0
def blog_details():
    """
    This allows blogger to create or edit a blog post - existing posts are
    linked to from the My SAFE page for the creator. These records are not
    visible to all users, who would just read the blog post.
    """

    # do we have a request for an existing blog post?
    blog_id = request.args(0)

    if blog_id is not None:
        record = db.blog_posts(blog_id)
    else:
        record = None

    if blog_id is not None and record is None:
        # avoid unknown blogs
        session.flash = B(CENTER('Invalid blog id'), _style='color:red;')
        redirect(URL('blogs', 'blogs'))

    elif record is None or record.user_id == auth.user.id or auth.has_membership(
            'admin'):

        if record is None:
            buttons = [
                TAG.BUTTON('Submit',
                           _type="submit",
                           _class="button btn btn-default",
                           _style='padding: 5px 15px 5px 15px;',
                           _name='create')
            ]
            readonly = False
        else:
            readonly = True if record.admin_status == 'Submitted' else False
            buttons = [
                TAG.BUTTON('Update and resubmit',
                           _type="submit",
                           _class="button btn btn-default",
                           _style='padding: 5px 15px 5px 15px;',
                           _name='update')
            ]

        # provide a form to create or edit
        form = SQLFORM(
            db.blog_posts,
            readonly=readonly,
            record=record,
            buttons=buttons,
            fields=['thumbnail_figure', 'authors', 'title', 'content'],
            showid=False)

        if form.validate(onvalidation=validate_blog_post):

            req_keys = list(request.vars.keys())

            # get and add a comment to the history
            hist_str = '[{}] {} {}\\n -- {}\\n'
            new_history = hist_str.format(
                datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                auth.user.first_name, auth.user.last_name,
                'Post created' if blog_id is None else "Post edited")

            if 'update' in req_keys:
                id = record.update_record(
                    admin_history=new_history + record.admin_history,
                    **db.blog_posts._filter_fields(form.vars))
                id = id.id
                msg = CENTER(
                    B('Blog post updated and resubmitted for approval.'),
                    _style='color: green')

            elif 'create' in req_keys:
                id = db.blog_posts.insert(admin_history=new_history,
                                          **db.blog_posts._filter_fields(
                                              form.vars))

                msg = CENTER(
                    B('Blog post created and submitted for approval.'),
                    _style='color: green')
            else:
                pass

            # Email the link
            template_dict = {
                'name':
                auth.user.first_name,
                'url':
                URL('blogs', 'blog_details', args=[id], scheme=True,
                    host=True),
                'submission_type':
                'blog post'
            }

            safe_mailer(to=auth.user.email,
                        subject='SAFE: blog post submitted',
                        template='generic_submitted.html',
                        template_dict=template_dict)

            session.flash = msg
            redirect(URL('blogs', 'blog_details', args=[id]))

        elif form.errors:
            response.flash = CENTER(B('Problems with the form, check below.'),
                                    _style='color: red')
        else:
            pass

        # package form into a panel
        if record is None:
            status = ""
            vis = ""
        else:
            status = DIV(
                approval_icons[record.admin_status],
                XML('&nbsp'),
                'Status: ',
                XML('&nbsp'),
                record.admin_status,
                _class='col-sm-3',
                _style=
                'padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;'
            )
            if record.hidden:
                vis = DIV(
                    'Hidden',
                    _class='col-sm-1 col-sm-offset-1',
                    _style=
                    'padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;'
                )
            else:
                vis = DIV(
                    'Visible',
                    _class='col-sm-1 col-sm-offset-1',
                    _style=
                    'padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;'
                )

        panel_header = DIV(H5('Blog post', _class='col-sm-7'),
                           status,
                           vis,
                           _class='row',
                           _style='margin:0px 0px')

        if readonly:
            content = XML(record.content)
        else:
            content = form.custom.widget.content

        form = FORM(
            form.custom.begin,
            DIV(DIV(panel_header, _class="panel-heading"),
                DIV(LABEL('Thumbnail Figure:',
                          _class="control-label col-sm-2"),
                    DIV(form.custom.widget.thumbnail_figure,
                        _class="col-sm-10"),
                    _class='row',
                    _style='margin:10px 10px'),
                DIV(LABEL('Authors:', _class="control-label col-sm-2"),
                    DIV(form.custom.widget.authors, _class="col-sm-10"),
                    _class='row',
                    _style='margin:10px 10px'),
                DIV(LABEL('Title:', _class="control-label col-sm-2"),
                    DIV(form.custom.widget.title, _class="col-sm-10"),
                    _class='row',
                    _style='margin:10px 10px'),
                DIV(LABEL('Blog Content:', _class="control-label col-sm-2"),
                    DIV(content, _class="col-sm-10"),
                    _class='row',
                    _style='margin:10px 10px'),
                DIV(form.custom.submit, _class='panel-footer'),
                _class="panel panel-primary"), form.custom.end)
    else:
        # security doesn't allow people editing other users blogs
        session.flash = CENTER(
            B('You do not have permission to edit this blog post.'),
            _style='color: red')
        redirect(URL('blogs', 'blog_post', args=blog_id))

    # admin history display
    if record is not None and record.admin_history is not None:
        admin_history = DIV(DIV(H5('Admin History', ), _class="panel-heading"),
                            DIV(XML(record.admin_history.replace(
                                '\\n', '<br />'),
                                    sanitize=True,
                                    permitted_tags=['br/']),
                                _class='panel_body'),
                            DIV(_class="panel-footer"),
                            _class='panel panel-primary')
    else:
        admin_history = DIV()

    ## ADMIN INTERFACE
    if record is not None and auth.has_membership(
            'admin') and record.admin_status == 'Submitted':

        admin = admin_decision_form(['Resubmit', 'Approved'])

        if admin.process(formname='admin').accepted:

            # update record with decision
            admin_str = '[{}] {} {}\\n ** Decision: {}\\n ** Comments: {}\\n'
            new_history = admin_str.format(
                datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                auth.user.first_name, auth.user.last_name, admin.vars.decision,
                admin.vars.comment) + record.admin_history

            record.update_record(admin_status=admin.vars.decision,
                                 admin_history=new_history)

            # pick an decision
            poster = record.user_id

            template_dict = {
                'name':
                poster.first_name,
                'url':
                URL('blogs',
                    'blog_details',
                    args=[blog_id],
                    scheme=True,
                    host=True),
                'public_url':
                URL('blogs',
                    'blog_post',
                    args=[blog_id],
                    scheme=True,
                    host=True),
                'admin':
                auth.user.first_name + ' ' + auth.user.last_name,
                'submission_type':
                'blog post'
            }

            # pick an decision
            if admin.vars.decision == 'Approved':

                safe_mailer(to=poster.email,
                            subject='SAFE: blog post approved',
                            template='generic_approved.html',
                            template_dict=template_dict)

                msg = CENTER(B('Blog approval emailed to poster at {}.'.format(
                    poster.email)),
                             _style='color: green')

            elif admin.vars.decision == 'Resubmit':

                safe_mailer(to=poster.email,
                            subject='SAFE: blog post requires resubmission',
                            template='generic_resubmit.html',
                            template_dict=template_dict)

                msg = CENTER(B(
                    'Blog resubmission emailed to poster at {}.'.format(
                        poster.email)),
                             _style='color: green')

            else:
                pass

            redirect(URL('blogs', 'administer_blogs'))
            session.flash = msg

        elif admin.errors:
            response.flash = CENTER(
                B('Errors in form, please check and resubmit'),
                _style='color: red')
        else:
            pass
    else:
        admin = DIV()

    # pass components to the view
    return dict(form=form, admin_history=admin_history, admin=admin)
예제 #6
0
def help_request_details():
    """
    This controller shows a SQLFORM to submit or update a request for help
    on a project. Only projects for which the logged in user is a 
    coordinator are available.
    
    The controller then passes the response through validation before 
    sending a confirmation email out.
    """

    # do we have a request for an existing help request post?
    request_id = request.args(0)

    if request_id is not None:
        record = db.help_request(request_id)
    else:
        record = None

    if request_id is not None and record is None:
        # avoid unknown blogs
        session.flash = B(CENTER('Invalid vacancy advert id'),
                          _style='color:red;')
        redirect(URL('marketplace', 'help_requests'))

    elif record is None or record.user_id == auth.user.id or auth.has_membership(
            'admin'):

        if record is None:
            buttons = [
                TAG.BUTTON('Submit',
                           _type="submit",
                           _class="button btn btn-default",
                           _style='padding: 5px 15px 5px 15px;',
                           _name='create')
            ]
            readonly = False
        else:
            readonly = True if record.admin_status == 'Submitted' else False
            fill_label = "Mark as available" if record.available else "Mark as unavailable"
            buttons = [
                TAG.BUTTON(fill_label,
                           _type="submit",
                           _class="button btn btn-default",
                           _style='padding: 5px 15px 5px 15px;',
                           _name='available'),
                XML('&nbsp;') * 5,
                TAG.BUTTON('Update and resubmit',
                           _type="submit",
                           _class="button btn btn-default",
                           _style='padding: 5px 15px 5px 15px;',
                           _name='update')
            ]

        # Restrict the project choices
        # - find the acceptable project ID numbers
        if auth.has_membership('admin'):
            # admins can advertise on any project
            query = db(db.project_details.project_id > 0)
        else:
            # otherwise coordinators can advertise on their own projects
            query = db((db.project_members.user_id == auth.user.id)
                       & (db.project_members.is_coordinator == 'T')
                       & (db.project_members.project_id == db.project_id.id)
                       & (db.project_details.project_id == db.project_id.id)
                       & (db.project_details.admin_status == 'Approved'))

        # - modify the help_request project_id requirements within this controller
        db.help_request.project_id.requires = IS_IN_DB(
            query, db.project_details.project_id, '%(title)s', zero=None)

        # check to see if anything is available
        if query.count() == 0:
            form = CENTER(
                B('You are not registered as a coordinator of any projects.'),
                _style='color: red')
        else:
            form = SQLFORM(db.help_request,
                           fields=[
                               'project_id', 'start_date', 'end_date',
                               'work_description', 'vacancy_type',
                               'paid_position', 'url'
                           ],
                           readonly=readonly,
                           record=record,
                           buttons=buttons,
                           showid=False)

            if form.validate(onvalidation=validate_help_request):

                req_keys = list(request.vars.keys())
                if 'available' in req_keys:
                    # provide a simple toggle option for the availability
                    hist_str = '[{}] {} {}\\n -- Vacancy marked as {}\\n'
                    new_history = hist_str.format(
                        datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                        auth.user.first_name, auth.user.last_name,
                        "available" if record.available else "unavailable")
                    id = record.update_record(available=not record.available,
                                              admin_history=new_history +
                                              record.admin_history)
                    id = id.id
                    msg = CENTER(B('Vacancy availability updated.'),
                                 _style='color: green')
                else:

                    # get and add a comment to the history
                    hist_str = '[{}] {} {}\\n -- {}\\n'
                    new_history = hist_str.format(
                        datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                        auth.user.first_name, auth.user.last_name,
                        'Vacancy advert created'
                        if request_id is None else "Vacancy advert updated")

                    if 'update' in req_keys:
                        id = record.update_record(
                            admin_status='Submitted',
                            admin_history=new_history + record.admin_history,
                            **db.help_request._filter_fields(form.vars))
                        id = id.id
                        msg = CENTER(B(
                            'Vacancy advert updated and resubmitted for approval.'
                        ),
                                     _style='color: green')
                    elif 'create' in req_keys:
                        id = db.help_request.insert(
                            admin_status='Submitted',
                            admin_history=new_history,
                            **db.help_request._filter_fields(form.vars))
                        msg = CENTER(B(
                            'Vacancy advert created and submitted for approval.'
                        ),
                                     _style='color: green')
                    else:
                        pass

                    # Email the link
                    template_dict = {
                        'name':
                        auth.user.first_name,
                        'url':
                        URL('marketplace',
                            'help_request_details',
                            args=[id],
                            scheme=True,
                            host=True),
                        'submission_type':
                        'project help request'
                    }

                    safe_mailer(to=auth.user.email,
                                subject='SAFE: Vacancy advert submitted',
                                template='generic_submitted.html',
                                template_dict=template_dict)

                session.flash = msg
                redirect(URL('marketplace', 'help_request_details', args=[id]))

            elif form.errors:
                response.flash = CENTER(
                    B('Problems with the form, check below.'),
                    _style='color: red')
            else:
                pass

            # package form into a panel
            if record is None:
                status = ""
                # vis = ""
            else:
                status = DIV(
                    approval_icons[record.admin_status],
                    XML('&nbsp'),
                    'Status: ',
                    XML('&nbsp'),
                    record.admin_status,
                    _class='col-sm-3',
                    _style=
                    'padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;'
                )
                # if record.hidden:
                #     vis = DIV('Hidden', _class='col-sm-1 col-sm-offset-1',
                #               _style='padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;')
                # else:
                #     vis = DIV('Visible', _class='col-sm-1 col-sm-offset-1',
                #               _style='padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;')

            panel_header = DIV(
                H5('Project vacancy advert', _class='col-sm-9'),
                status,  # vis,
                _class='row',
                _style='margin:0px 10px')

            # package in controller
            if not readonly:
                form.custom.widget.work_description['_rows'] = 4
                form.custom.widget.start_date[
                    '_class'] = "form-control input-sm"
                form.custom.widget.end_date['_class'] = "form-control input-sm"

            # set up availability header
            if record is not None:
                if record.available:
                    avail = DIV(LABEL('Availability:',
                                      _class="control-label col-sm-2"),
                                DIV("Vacancy is currently available",
                                    _class="col-sm-10"),
                                _class='row',
                                _style='margin:10px 10px')
                else:
                    avail = DIV(LABEL('Availability:',
                                      _class="control-label col-sm-2"),
                                DIV("Vacancy is  marked as unavailable",
                                    _class="col-sm-10"),
                                _class='row',
                                _style='margin:10px 10px')
            else:
                # blank DIV for new headers
                avail = DIV()

            # locally update the description representation to allow formatting
            db.help_request.work_description.represent = lambda text, row: PRE(
                text)

            form = FORM(
                DIV(DIV(panel_header, _class="panel-heading"),
                    DIV(form.custom.begin,
                        avail,
                        DIV(LABEL('Project:', _class="control-label col-sm-2"),
                            DIV(form.custom.widget.project_id,
                                _class="col-sm-10"),
                            _class='row',
                            _style='margin:10px 10px'),
                        DIV(LABEL('Work description:',
                                  _class="control-label col-sm-2"),
                            DIV(form.custom.widget.work_description,
                                _class="col-sm-10"),
                            _class='row',
                            _style='margin:10px 10px'),
                        DIV(LABEL('Dates:', _class="control-label col-sm-2"),
                            DIV(DIV(form.custom.widget.start_date,
                                    SPAN('to',
                                         _class="input-group-addon input-sm"),
                                    form.custom.widget.end_date,
                                    _class="input-daterange input-group",
                                    _id="help_datepicker"),
                                _class='col-sm-10'),
                            _class='row',
                            _style='margin:10px 10px'),
                        HR(),
                        DIV(P(
                            'Choose a vacancy type. If the post is paid, check the box and where '
                            'possible provide a link for any further details and the application procedure.'
                        ),
                            _class='row',
                            _style='margin:10px 10px'),
                        DIV(LABEL('Vacancy type:',
                                  _class="control-label col-sm-2"),
                            DIV(form.custom.widget.vacancy_type,
                                _class='col-sm-7'),
                            LABEL(form.custom.widget.paid_position,
                                  'Paid Position',
                                  _class="control-label col-sm-3"),
                            _class='row',
                            _style='margin:10px 10px'),
                        DIV(LABEL('Website for details',
                                  _class="control-label col-sm-2"),
                            DIV(form.custom.widget.url, _class="col-sm-10"),
                            _class='row',
                            _style='margin:10px 10px'),
                        DIV(DIV(form.custom.submit,
                                _class="col-sm-10 col-sm-offset-2"),
                            _class='row',
                            _style='margin:10px 10px'),
                        form.custom.end,
                        _class='panel_body'),
                    _class="panel panel-primary"),
                datepicker_script(html_id='help_datepicker',
                                  autoclose='true',
                                  startDate='"+0d"',
                                  endDate='""'))

    else:
        # security doesn't allow people editing other users blogs
        session.flash = CENTER(
            B('You do not have permission to edit this vacancy advert.'),
            _style='color: red')
        redirect(URL('marketplace', 'help_requests', args=request_id))

    # admin history display
    if record is not None and record.admin_history is not None:
        admin_history = DIV(DIV(H5('Admin History', ), _class="panel-heading"),
                            DIV(XML(record.admin_history.replace(
                                '\\n', '<br />'),
                                    sanitize=True,
                                    permitted_tags=['br/']),
                                _class='panel_body'),
                            DIV(_class="panel-footer"),
                            _class='panel panel-primary')
    else:
        admin_history = DIV()

    ## ADMIN INTERFACE
    if record is not None and auth.has_membership(
            'admin') and record.admin_status == 'Submitted':

        admin = admin_decision_form(['Resubmit', 'Approved'])

        if admin.process(formname='admin').accepted:

            # update record with decision
            admin_str = '[{}] {} {}\\n ** Decision: {}\\n ** Comments: {}\\n'
            new_history = admin_str.format(
                datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                auth.user.first_name, auth.user.last_name, admin.vars.decision,
                admin.vars.comment) + record.admin_history

            record.update_record(admin_status=admin.vars.decision,
                                 admin_history=new_history)

            # pick an decision
            poster = record.user_id

            template_dict = {
                'name':
                poster.first_name,
                'url':
                URL('marketplace',
                    'help_request_details',
                    args=[request_id],
                    scheme=True,
                    host=True),
                'public_url':
                URL('marketplace',
                    'view_help_request',
                    args=[request_id],
                    scheme=True,
                    host=True),
                'admin':
                auth.user.first_name + ' ' + auth.user.last_name,
                'submission_type':
                'project help request'
            }

            # pick an decision
            if admin.vars.decision == 'Approved':

                safe_mailer(to=poster.email,
                            subject='SAFE: vacancy advert approved',
                            template='generic_approved.html',
                            template_dict=template_dict)

                msg = CENTER(B(
                    'Vacancy advert approval emailed to poster at {}.'.format(
                        poster.email)),
                             _style='color: green')

            elif admin.vars.decision == 'Resubmit':

                safe_mailer(
                    to=poster.email,
                    subject='SAFE: Vacancy advert requires resubmission',
                    template='generic_resubmit.html',
                    template_dict=template_dict)

                msg = CENTER(
                    B('Vacancy advert resubmission emailed to poster at {}.'.
                      format(poster.email)),
                    _style='color: green')

            else:
                pass

            redirect(URL('marketplace', 'administer_help_requests'))
            session.flash = msg

        elif admin.errors:
            response.flash = CENTER(
                B('Errors in form, please check and resubmit'),
                _style='color: red')
        else:
            pass
    else:
        admin = DIV()

    # pass components to the view
    return dict(form=form, admin_history=admin_history, admin=admin)
예제 #7
0
def volunteer_details():
    """
    This controller shows a SQLFORM to submit or update an offer to
    volunteer at the SAFE project.
    
    The controller then passes the response through validation before 
    sending a confirmation email out.
    """

    # do we have a request for an existing help request post?
    request_id = request.args(0)

    if request_id is not None:
        record = db.help_offered(request_id)
    else:
        record = None

    if request_id is not None and record is None:
        # avoid unknown blogs
        session.flash = B(CENTER('Invalid volunteer offer id'),
                          _style='color:red;')
        redirect(URL('marketplace', 'volunteers'))

    elif record is None or record.user_id == auth.user.id or auth.has_membership(
            'admin'):

        if record is None:
            buttons = [
                TAG.BUTTON('Submit',
                           _type="submit",
                           _class="button btn btn-default",
                           _style='padding: 5px 15px 5px 15px;',
                           _name='create')
            ]
            readonly = False
        else:
            readonly = True if record.admin_status == 'Submitted' else False
            buttons = [
                TAG.BUTTON('Update and resubmit',
                           _type="submit",
                           _class="button btn btn-default",
                           _style='padding: 5px 15px 5px 15px;',
                           _name='update')
            ]

        db.help_offered.research_areas.comment = 'Select at least one research area of interest to help match you to projects.'

        form = SQLFORM(db.help_offered,
                       fields=[
                           'volunteer_type', 'statement_of_interests',
                           'research_areas', 'available_from', 'available_to'
                       ],
                       record=record,
                       buttons=buttons,
                       comments=True,
                       showid=False,
                       readonly=readonly)

        if form.validate(onvalidation=validate_volunteer):

            req_keys = list(request.vars.keys())

            # get and add a comment to the history
            hist_str = '[{}] {} {}\\n -- {}\\n'
            new_history = hist_str.format(
                datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                auth.user.first_name, auth.user.last_name,
                'Volunteer offer created'
                if request_id is None else "Volunteer offer updated")

            if 'update' in req_keys:
                id = record.update_record(
                    admin_status='Submitted',
                    admin_history=new_history + record.admin_history,
                    **db.help_offered._filter_fields(form.vars))
                id = id.id
                msg = CENTER(
                    B('Volunteer offer updated and resubmitted for approval.'),
                    _style='color: green')
            elif 'create' in req_keys:
                id = db.help_offered.insert(admin_status='Submitted',
                                            admin_history=new_history,
                                            **db.help_offered._filter_fields(
                                                form.vars))
                msg = CENTER(
                    B('Volunteer offer created and submitted for approval.'),
                    _style='color: green')
            else:
                pass

            # Email the link
            template_dict = {
                'name':
                auth.user.first_name,
                'url':
                URL('marketplace',
                    'volunteer_details',
                    args=[id],
                    scheme=True,
                    host=True),
                'submission_type':
                'volunteer offer'
            }

            safe_mailer(to=auth.user.email,
                        subject='SAFE: volunteer offer submitted',
                        template='generic_submitted.html',
                        template_dict=template_dict)

            session.flash = msg
            redirect(URL('marketplace', 'volunteer_details', args=[id]))

        elif form.errors:
            response.flash = CENTER(B('Problems with the form, check below.'),
                                    _style='color: red')
        else:
            pass

        # package in controller
        if not readonly:
            form.custom.widget.statement_of_interests['_rows'] = 4
            form.custom.widget.available_from[
                '_class'] = "form-control input-sm"
            form.custom.widget.available_to['_class'] = "form-control input-sm"

        if record is None:
            status = ""
            # vis = ""
        else:
            status = DIV(
                approval_icons[record.admin_status],
                XML('&nbsp'),
                'Status: ',
                XML('&nbsp'),
                record.admin_status,
                _class='col-sm-3',
                _style=
                'padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;'
            )
            # if record.hidden:
            #     vis = DIV('Hidden', _class='col-sm-1 col-sm-offset-1',
            #               _style='padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;')
            # else:
            #     vis = DIV('Visible', _class='col-sm-1 col-sm-offset-1',
            #               _style='padding: 5px 15px 5px 15px;background-color:lightgrey;color:black;')

        panel_header = DIV(
            H5('Project help request', _class='col-sm-9'),
            status,  # vis,
            _class='row',
            _style='margin:0px 10px')

        form = FORM(
            DIV(DIV(panel_header, _class="panel-heading"),
                DIV(form.custom.begin,
                    DIV(LABEL('Volunteer type:',
                              _class="control-label col-sm-2"),
                        DIV(form.custom.widget.volunteer_type,
                            _class="col-sm-10"),
                        _class='row',
                        _style='margin:10px 10px'),
                    DIV(LABEL('Statement of interests:',
                              _class="control-label col-sm-2"),
                        DIV(form.custom.widget.statement_of_interests,
                            _class="col-sm-10"),
                        _class='row',
                        _style='margin:10px 10px'),
                    DIV(LABEL('Research areas:',
                              _class="control-label col-sm-2"),
                        DIV(form.custom.widget.research_areas,
                            _class="col-sm-10"),
                        _class='row',
                        _style='margin:10px 10px'),
                    DIV(LABEL('Dates:', _class="control-label col-sm-2"),
                        DIV(DIV(form.custom.widget.available_from,
                                SPAN('to',
                                     _class="input-group-addon input-sm"),
                                form.custom.widget.available_to,
                                _class="input-daterange input-group",
                                _id="vol_datepicker"),
                            _class='col-sm-10'),
                        _class='row',
                        _style='margin:10px 10px'),
                    DIV(DIV(form.custom.submit,
                            _class="col-sm-10 col-sm-offset-2"),
                        _class='row',
                        _style='margin:10px 10px'),
                    form.custom.end,
                    _class='panel_body'),
                _class="panel panel-primary"),
            datepicker_script(html_id='vol_datepicker',
                              autoclose='true',
                              startDate='"+0d"',
                              endDate='"+365d"'))
    else:
        # security doesn't allow people editing other users volunteer offers
        session.flash = CENTER(
            B('You do not have permission to edit this volunteer offer.'),
            _style='color: red')
        redirect(URL('marketplace', 'volunteers', args=request_id))

    # admin history display
    if record is not None and record.admin_history is not None:
        admin_history = DIV(DIV(H5('Admin History', ), _class="panel-heading"),
                            DIV(XML(record.admin_history.replace(
                                '\\n', '<br />'),
                                    sanitize=True,
                                    permitted_tags=['br/']),
                                _class='panel_body'),
                            DIV(_class="panel-footer"),
                            _class='panel panel-primary')
    else:
        admin_history = DIV()

    ## ADMIN INTERFACE
    if record is not None and auth.has_membership(
            'admin') and record.admin_status == 'Submitted':

        admin = admin_decision_form(['Resubmit', 'Approved'])

        if admin.process(formname='admin').accepted:

            # update record with decision
            admin_str = '[{}] {} {}\\n ** Decision: {}\\n ** Comments: {}\\n'
            new_history = admin_str.format(
                datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                auth.user.first_name, auth.user.last_name, admin.vars.decision,
                admin.vars.comment) + record.admin_history

            record.update_record(admin_status=admin.vars.decision,
                                 admin_history=new_history)

            # pick an decision
            poster = record.user_id

            template_dict = {
                'name':
                poster.first_name,
                'url':
                URL('marketplace',
                    'volunteer_details',
                    args=[request_id],
                    scheme=True,
                    host=True),
                'public_url':
                URL('marketplace',
                    'view_volunteer',
                    args=[request_id],
                    scheme=True,
                    host=True),
                'admin':
                auth.user.first_name + ' ' + auth.user.last_name,
                'submission_type':
                'volunteer offer'
            }

            # pick an decision
            if admin.vars.decision == 'Approved':

                safe_mailer(to=poster.email,
                            subject='SAFE: volunteer offer approved',
                            template='generic_approved.html',
                            template_dict=template_dict)

                msg = CENTER(B(
                    'Volunteer offer approval emailed to poster at {}.'.format(
                        poster.email)),
                             _style='color: green')

            elif admin.vars.decision == 'Resubmit':

                safe_mailer(
                    to=poster.email,
                    subject='SAFE: volunteer offer requires resubmission',
                    template='generic_resubmit.html',
                    template_dict=template_dict)

                msg = CENTER(
                    B('Volunteer offer resubmission emailed to poster at {}.'.
                      format(poster.email)),
                    _style='color: green')

            else:
                pass

            redirect(URL('marketplace', 'administer_volunteers'))
            session.flash = msg

        elif admin.errors:
            response.flash = CENTER(
                B('Errors in form, please check and resubmit'),
                _style='color: red')
        else:
            pass
    else:
        admin = DIV()

    # pass components to the view
    return dict(form=form, admin_history=admin_history, admin=admin)
예제 #8
0
def output_details():
    """
    This controller provides an interface to create a new output 
    (when no request arguments are passed)
    
    shows a SQLFORM to submit an upload and it also exposes a form
    for existing users to tag that upload to a project and only
    projects for which the logged in user is a member are available.
    
    The controller then passes the response through validation before 
    sending a confirmation email out.
    """

    # do we have a request for an existing blog post
    output_id = request.args(0)

    if output_id is not None:
        record = db.outputs(output_id)
        buttons = [
            TAG.button('Save edits',
                       _type="submit",
                       _name='save',
                       _style='padding: 5px 15px 5px 15px;'),
            XML('&nbsp;') * 5,
            TAG.button('Submit output',
                       _type="submit",
                       _name='submit',
                       _style='padding: 5px 15px 5px 15px;')
        ]
    else:
        record = None
        buttons = [
            TAG.button('Create output',
                       _type="submit",
                       _name='create',
                       _style='padding: 5px 15px 5px 15px;')
        ]

    if output_id is not None and record is None:

        # avoid unknown outputs
        session.flash = B(CENTER('Invalid output id'), _style='color:red;')
        redirect(URL('outputs', 'outputs'))

    elif output_id is not None and ((record.user_id != auth.user.id) &
                                    (not auth.has_membership('admin'))):
        # security check to stop people editing other users outputs
        session.flash = CENTER(B(
            'You do not have permission to edit these output details, showing output view.'
        ),
                               _style='color: red')
        redirect(URL('outputs', 'view_output', args=output_id))

    else:

        # allow admins to view but not edit existing records and lock submitted versions
        if (record is not None) and ((record.user_id != auth.user.id) or
                                     (record.admin_status == 'Submitted')):
            readonly = True
        else:
            readonly = False

        # create the form
        form = SQLFORM(db.outputs,
                       record=output_id,
                       fields=[
                           'thumbnail_figure', 'file', 'title', 'abstract',
                           'lay_summary', 'format', 'citation', 'doi', 'url'
                       ],
                       readonly=readonly,
                       showid=False,
                       buttons=buttons)

        # now intercept and parse the various inputs
        if form.process(onvalidation=validate_output_details,
                        formname='outputs').accepted:

            # reload the record
            output_record = db.outputs(form.vars.id)

            template_dict = {
                'name':
                auth.user.first_name,
                'url':
                URL('outputs',
                    'output_details',
                    args=[output_record.id],
                    scheme=True,
                    host=True)
            }

            if form.submit:

                # Submit button pressed: Signal success and email the proposer
                safe_mailer(to=auth.user.email,
                            subject='SAFE: project output submitted',
                            template='output_submitted.html',
                            template_dict=template_dict)

                session.flash = CENTER(B('SAFE project output submitted.'),
                                       _style='color: green')

                # create a comment to add to the history
                history_text = 'Output submitted'

                # update the status
                output_record.update_record(
                    submission_date=datetime.date.today(),
                    admin_status='Submitted')

            else:
                # is this brand new? If so send a link
                if record is None:
                    safe_mailer(to=auth.user.email,
                                subject='SAFE: project output draft created',
                                template='output_created.html',
                                template_dict=template_dict)

                    session.flash = CENTER(
                        B('SAFE project output draft created.'),
                        _style='color: green')

                    # create a comment to add to the history
                    history_text = 'Output draft created'

                else:
                    history_text = 'Output draft edited'
                    session.flash = CENTER(
                        B('SAFE project output draft created.'),
                        _style='color: green')

                    # create a comment to add to the history
                    history_text = 'Output edited'

                # update the status - takes output back from approved
                output_record.update_record(admin_status='Draft')

            new_history = '[{}] {} {}\\n -- {}\\n'.format(
                datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                auth.user.first_name, auth.user.last_name, history_text)

            if output_record.admin_history is None or output_record.admin_history == '':
                output_record.update_record(admin_history=new_history)
            else:
                output_record.update_record(admin_history=new_history +
                                            output_record.admin_history)

            # now send to the output_details page for the form we've just created
            redirect(URL('outputs', 'output_details', args=form.vars.id))

        elif form.errors:
            response.flash = CENTER(
                B('Errors in form, please check and resubmit'),
                _style='color: red')
        else:
            pass

        # now repackage the form as a more attractive DIV
        if record is not None:
            # - thumbnail_figure
            if (record is None) or (record.thumbnail_figure
                                    in [None, 'NA', '']):
                pic = URL('static',
                          'images/default_thumbnails/missing_output.png')
            else:
                pic = URL('default', 'download', args=record.thumbnail_figure)

            # - file
            if record.file not in [None, 'NA', '']:
                # need to retrieve the file to get the original file name
                fn, stream = db.outputs.file.retrieve(record.file)
                stream.close()
                dfile = A(fn,
                          _href=URL('default', 'download', args=record.file))
            else:
                dfile = ""

            uploader = DIV(
                'Uploaded by: ',
                record.user_id.first_name,
                ' ',
                record.user_id.last_name,
                _style='text-align:right; color=grey; font-size:smaller',
                _class='col-sm-8')
        else:
            pic = URL('static', 'images/default_thumbnails/missing_output.png')
            dfile = ""
            uploader = DIV()

        # make it clear the DOI should be a link
        if not readonly:
            form.custom.widget.doi["_placeholder"] = "http://dx.doi.org/"

        form = CAT(
            form.custom.begin,
            DIV(DIV(H5('Output details', ), _class="panel-heading"),
                DIV(DIV(LABEL('Title:', _class="control-label col-sm-2"),
                        DIV(form.custom.widget.title, _class="col-sm-10"),
                        _class='row'),
                    DIV(DIV(DIV(LABEL('Upload Thumbnail:',
                                      _class="control-label col-sm-4"),
                                DIV(form.custom.widget.thumbnail_figure,
                                    _class="col-sm-8"),
                                _class='row'),
                            DIV(LABEL('Current Thumbnail:',
                                      _class="control-label col-sm-4"),
                                DIV(IMG(_src=pic, _height='100px'),
                                    _class='col-sm-8'),
                                _class='row'),
                            _class='col-sm-6'),
                        DIV(DIV(LABEL('Upload Output File:',
                                      _class="control-label col-sm-4"),
                                DIV(form.custom.widget.file,
                                    _class="col-sm-8"),
                                _class='row'),
                            DIV(LABEL('Current Output File:',
                                      _class="control-label col-sm-4"),
                                DIV(dfile, _class='col-sm-8'),
                                _class='row'),
                            _class='col-sm-6'),
                        _class='row'),
                    DIV(LABEL('Scientific Abstract:',
                              _class="control-label col-sm-2"),
                        DIV(form.custom.widget.abstract, _class="col-sm-10"),
                        _class='row'),
                    DIV(LABEL('Lay Summary or Press Release:',
                              _class="control-label col-sm-2"),
                        DIV(form.custom.widget.lay_summary,
                            _class="col-sm-10"),
                        _class='row'),
                    DIV(LABEL('Format:', _class="control-label col-sm-2"),
                        DIV(form.custom.widget.format, _class="col-sm-10"),
                        _class='row'),
                    DIV(LABEL('Citation:', _class="control-label col-sm-2"),
                        DIV(form.custom.widget.citation, _class="col-sm-10"),
                        _class='row'),
                    DIV(LABEL('URL for DOI:', _class="control-label col-sm-2"),
                        DIV(form.custom.widget.doi, _class="col-sm-10"),
                        _class='row'),
                    DIV(LABEL('URL:', _class="control-label col-sm-2"),
                        DIV(form.custom.widget.url, _class="col-sm-10"),
                        _class='row'),
                    _class='panel_body',
                    _style='margin:10px 10px'),
                DIV(DIV(DIV(form.custom.submit, _class='col-sm-4'),
                        uploader,
                        _class='row'),
                    _class='panel-footer'),
                _class="panel panel-primary"), form.custom.end)

        # Now handle adding projects:
        # - provide records of the projects already added and a restricted set
        #   of projects that the user can chose
        if output_id is not None:

            # current projects
            projects_query = db(
                (db.project_outputs.output_id == output_id)
                & (db.project_outputs.project_id == db.project_id.id)
                & (db.project_id.project_details_id == db.project_details.id))

            if projects_query.count() > 0:

                # select the rows and wrap up into a TABLE within a panel DIV
                projects_select = projects_query.select()
                projects_rows = [
                    TR(
                        TD(
                            A(r.project_details.title,
                              _href=URL('projects',
                                        'project_view',
                                        args=r.project_details.project_id))),
                        TD()) for r in projects_select
                ]
            else:
                projects_rows = []

            # wrap everything into a form with a list of projects to select
            linkable_query = db(
                (db.project_id.id == db.project_details.project_id)
                & (~db.project_id.id.belongs(
                    projects_query.select(db.project_id.id))))
            linkable = linkable_query.select(db.project_id.id,
                                             db.project_details.title,
                                             orderby=db.project_id.id)

            selector = SELECT(*[
                OPTION('({}) {}'.format(r.project_id.id,
                                        r.project_details.title),
                       _value=r.project_id.id) for r in linkable
            ],
                              _class="generic-widget form-control",
                              _name='project_id')
            add_button = TAG.BUTTON(add_member_icon,
                                    _type="submit",
                                    _style='background:none; border:none;'
                                    'padding:0px 10px;font-size: 100%;')

            projects_rows.append(TR(selector, add_button))

            projects = FORM(
                DIV(DIV(H5('Associated projects', ), _class="panel-heading"),
                    TABLE(*projects_rows,
                          _width='100%',
                          _class='table table-striped'),
                    DIV(_class="panel-footer"),
                    _class='panel panel-primary'))

            if projects.process(formname='projects').accepted:

                # add the selected_project
                db.project_outputs.insert(project_id=projects.vars.project_id,
                                          output_id=output_id,
                                          user_id=auth.user.id,
                                          date_added=datetime.datetime.now())

                # look for merged projects and add to the umbrella project as well
                project_record = db.project_id[projects.vars.project_id]
                if project_record.merged_to is not None:
                    db.project_outputs.insert(
                        project_id=project_record.merged_to,
                        output_id=output_id,
                        user_id=auth.user.id,
                        date_added=datetime.datetime.now())

                session.flash = CENTER(B('Output added to new project.'),
                                       _style='color: green')
                redirect(URL('outputs', 'output_details', args=output_id))
            elif projects.errors:
                response.flash = CENTER(B('Problem adding project.'),
                                        _style='color: red')
            else:
                pass

        else:
            projects = DIV()

    # admin history display
    if record is not None and record.admin_history is not None:
        admin_history = DIV(DIV(H5('Admin History', ), _class="panel-heading"),
                            DIV(XML(record.admin_history.replace(
                                '\\n', '<br />'),
                                    sanitize=True,
                                    permitted_tags=['br/']),
                                _class='panel_body'),
                            DIV(_class="panel-footer"),
                            _class='panel panel-primary')
    else:
        admin_history = DIV()

    # header text
    if record is None:
        header = CAT(
            H2('New Output Submission'),
            P(
                'Please use the form below to create a new draft research output for the SAFE project. ',
                'Once you have created the draft you will then be able to ',
                B('link your output '),
                'to existing projects, make further edits and submit the completed outputs to the administrators.'
            ),
            P(
                'Once you submit your output it will first be screened by an administrator. You will get an ',
                'email to confirm that you have submitted an output and then another email to confirm ',
                'whether the output has been accepted or not.'))
    else:
        header = CAT(
            H2(approval_icons[record.admin_status] + XML('&nbsp;') * 3 +
               record.title),
            P('Please use the form to edit your draft output and ',
              B('link your output '),
              'to existing projects. When you have finished, click Submit.'),
            P(
                'Once you submit your output it will first be screened by an administrator. You will get an ',
                'email to confirm that you have submitted an output and then another email to confirm ',
                'whether the output has been accepted or not.'))

    # Add an admin interface
    if record is not None and auth.has_membership('admin'):

        admin = admin_decision_form(selector_options=['Resubmit', 'Approved'])

        if admin.process(formname='admin').accepted:

            # add info to the history string
            admin_str = '[{}] {} {}\\n ** Decision: {}\\n ** Comments: {}\\n'
            new_history = admin_str.format(
                datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%MZ'),
                auth.user.first_name, auth.user.last_name, admin.vars.decision,
                admin.vars.comment)

            # update the admin history
            record.update_record(admin_status=admin.vars.decision,
                                 admin_history=new_history +
                                 record.admin_history)

            # set a flash message
            flash_message = CENTER(B(
                'Decision emailed to project member at {}.'.format(
                    record.user_id.email)),
                                   _style='color: green')

            # email the submitter
            admin_template_dict = {
                'name':
                record.user_id.first_name,
                'url':
                URL('outputs',
                    'output_details',
                    args=[record.id],
                    scheme=True,
                    host=True),
                'public_url':
                URL('outputs',
                    'view_output',
                    args=[record.id],
                    scheme=True,
                    host=True),
                'admin':
                auth.user.first_name + ' ' + auth.user.last_name
            }

            if admin.vars.decision == 'Approved':

                safe_mailer(to=record.user_id.email,
                            subject='SAFE: project output approved',
                            template='output_approved.html',
                            template_dict=admin_template_dict)

                session.flash = flash_message

            elif admin.vars.decision == 'Resubmit':

                safe_mailer(to=record.user_id.email,
                            subject='SAFE: resubmit project output',
                            template='output_resubmit.html',
                            template_dict=admin_template_dict)

                session.flash = flash_message
            else:
                pass

            # reload
            redirect(URL('outputs', 'output_details', args=output_id))
    else:
        admin = DIV()

    return dict(form=form,
                projects=projects,
                admin_history=admin_history,
                header=header,
                admin=admin)
예제 #9
0
def remind_about_unknowns():
    """
    Daily emails to research visit proposers who haven't
    completed the visitor details for visits that start 
    within the next week.
    """

    db = current.db

    # get a list of research visits with <= a week to go.
    today = datetime.date.today()
    one_week_from_now = today + datetime.timedelta(days=7)

    # Select a summary to populate the task - implement
    # the SQL query below using the DAL:
    #   select a.first_name, a.email, v.title, v.id, count(m.id)
    #       from auth_user a join research_visit v on (a.id = v.proposer_id)
    #           join research_visit_member m on (m.research_visit_id = v.id)
    #       where m.user_id is null and
    #             v.arrival_date <= now() + interval '7' day and
    #             v.arrival_date > now()
    #       group by a.first_name, a.email, v.title, v.id;

    # get the query structure of the data
    query = db(
        (db.research_visit.arrival_date <= one_week_from_now)
        & (db.research_visit.arrival_date > today)
        & (db.auth_user.id == db.research_visit.proposer_id)
        & (db.research_visit_member.research_visit_id == db.research_visit.id)
        & (db.research_visit_member.user_id is None))

    offenders = query.select(
        db.auth_user.first_name,
        db.auth_user.email,
        db.research_visit.title,
        db.research_visit.id,
        db.research_visit_member.id.count().with_alias('count'),
        groupby=[
            db.auth_user.first_name, db.auth_user.email, db.research_visit.id,
            db.research_visit.title
        ])

    # now email each offender
    for offence in offenders:

        # dictionary to fill out the template
        template_dict = {
            'name':
            offence.auth_user.first_name,
            'title':
            offence.research_visit.title,
            'count':
            offence.count,
            'url':
            URL('research_visit',
                'research_visit_details',
                args=[offence.research_visit.id],
                scheme='https',
                host='www.safeproject.net')
        }
        # email this offender
        safe_mailer(subject='SAFE Research Visit details',
                    to=offence.auth_user.email,
                    template='research_visit_details_reminder.html',
                    template_dict=template_dict)

        # commit changes to the db - necessary for things running from models
        db.commit()

    ids = [str(o.research_visit.id) for o in offenders]
    if len(ids) > 0:
        return 'Emailed proposers of the following research visits: ' + ','.join(
            ids)
    else:
        return 'No incomplete research visits found within the next week'
예제 #10
0
def outdated_health_and_safety():
    """
    Daily emails to upcoming research visitors who haven't created their health
    and safety at all or haven't visited that link in more than 6 months
    """

    db = current.db

    # Get some timestamps
    now = datetime.datetime.now()
    old_hs = (now - datetime.timedelta(days=180)).date()

    # Find people with bed reservations at SAFE or Maliau who have old or missing H&S
    # There may be a way of combining these two but it seems likely to be slower
    safe_off = db((db.bed_reservations_safe.arrival_date - now <= 14)
                  & (db.bed_reservations_safe.departure_date >= now)
                  & (db.bed_reservations_safe.research_visit_member_id ==
                     db.research_visit_member.id)
                  & (db.research_visit_member.user_id == db.auth_user.id)
                  & ((db.health_and_safety.id == None) |
                     (db.health_and_safety.date_last_edited < old_hs))).select(
                         db.auth_user.ALL,
                         db.health_and_safety.ALL,
                         left=db.health_and_safety.on(
                             db.auth_user.id == db.health_and_safety.user_id),
                         distinct=True,
                         orderby=(db.auth_user.last_name,
                                  db.auth_user.first_name))

    mali_off = db((db.bed_reservations_maliau.arrival_date - now <= 14)
                  & (db.bed_reservations_maliau.departure_date >= now)
                  & (db.bed_reservations_maliau.research_visit_member_id ==
                     db.research_visit_member.id)
                  & (db.research_visit_member.user_id == db.auth_user.id)
                  & ((db.health_and_safety.id == None) |
                     (db.health_and_safety.date_last_edited < old_hs))).select(
                         db.auth_user.ALL,
                         db.health_and_safety.ALL,
                         left=db.health_and_safety.on(
                             db.auth_user.id == db.health_and_safety.user_id),
                         distinct=True,
                         orderby=(db.auth_user.last_name,
                                  db.auth_user.first_name))

    offenders = safe_off + mali_off

    # now email each offender
    for offence in offenders:

        # dictionary to fill out the template
        template_dict = {'name': offence.auth_user.first_name}

        # email this offender
        safe_mailer(
            subject='SAFE Research Visit health and safety information',
            to=offence.auth_user.email,
            template='research_visit_health_and_safety.html',
            template_dict=template_dict)

        # commit changes to the db - necessary for things running from models
        db.commit()

    if len(offenders) > 0:
        offender_names = ', '.join([
            '{first_name} {last_name}'.format(**rw.auth_user)
            for rw in offenders
        ])
        return 'Emailed {} researchers with outdated or missing H&S info: {}'.format(
            len(offenders), offender_names)
    else:
        return 'All H&S up to date'