Changeset ad530b4


Ignore:
Timestamp:
Aug 26, 2010, 10:55:42 PM (15 years ago)
Author:
Alex Dehnert <adehnert@…>
Branches:
master, client
Children:
5cfd6f6
Parents:
0df317c (diff), af48a00 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
git-author:
Alex Dehnert <adehnert@…> (08/26/10 22:55:42)
git-committer:
Alex Dehnert <adehnert@…> (08/26/10 22:55:42)
Message:

Merge branch 'bulk-actions'

Location:
remit
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • remit/media/style/style.css

    re1086bd raf48a00  
    9696    font-family: "Courier", monospace;
    9797}
     98
     99.select-modifiers li
     100{
     101    display: inline;
     102}
     103.select-modifiers li a
     104{
     105    border: 1px solid black;
     106    padding: 0.25em;
     107}
  • remit/remit_templates/base.html

    r0671644 r5865e5f  
    77    <link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}style/style.css" />
    88    <link rel="stylesheet" type="text/css" href="{{ MEDIA_URL }}style/default.css" />
     9    <script type="text/javascript"
     10        src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
     11
    912
    1013  </head>
  • remit/remit_templates/vouchers/reimbursementrequest_list.html

    r33cb144 raf48a00  
    1313{%endif%}
    1414
    15 <form>
     15<form method='get' action=''>
     16<h3>Filter and Sort</h3>
     17
    1618<table class='pretty-table'>
    1719<tr>
     
    3335</tr>
    3436<tr>
    35     <th colspan='2'><input type='submit' name='submit' value='Submit'></th>
     37    <th colspan='2'><input type='submit' name='submit' value='Submit' /></th>
    3638</tr>
    3739</table>
     
    4042<p><br /></p>
    4143
    42 <table class='pretty-table'>
     44<form method='post' action=''>
     45<h3>Request List</h3>
     46
     47<table class='pretty-table' id='request-list'>
    4348<tr>
    4449    <th>#</th>
     
    4954    <th>Approval state (date)</th>
    5055</tr>
    51 
    5256{% for object in object_list %}
    5357<tr>
    54     <td><a href='{% url review_request object.pk %}'>{{ object.pk }}</a></td>
     58    <td>
     59        <input type='checkbox' name='select' value='{{object.pk}}'{%ifin object.pk selected_ids %} checked='checked'{%endifin%}></input>
     60        <a href='{% url review_request object.pk %}'>{{ object.pk }}</a>
     61    </td>
    5562    <td>
    5663        <a href='{% url review_request object.pk %}'>{{ object.name }}</a>
     
    7784</table>
    7885
     86<ul class='select-modifiers'>
     87<li><a id='select-all'   href='#'>Select All</a></li>
     88<li><a id='select-none'  href='#'>Select None</a></li>
     89<li><a id='select-other' href='#'>Invert Selection</a></li>
     90</ul>
     91
     92<script type='text/javascript'>
     93$('#select-all').click(function(){
     94    $('#request-list input:checkbox').attr('checked', 'checked');
     95    return false;
     96});
     97$('#select-none').click(function(){
     98    $('#request-list input:checkbox').removeAttr('checked');
     99    return false;
     100});
     101$('#select-other').click(function(){
     102    $('#request-list input:checkbox').each(function(){
     103        this.checked = !this.checked;
     104    });
     105    return false;
     106});
     107</script>
     108
     109<h3>Bulk Actions</h3>
     110
     111<p>
     112    <select name='action'>
     113        <option value="none" selected="selected">-----------</option>
     114        {% for action in actions %}
     115        <option value='{{action.name}}'>{{action.label}}</option>
     116        {%endfor%}
     117    </select>
     118    <input type='submit' name='apply-action' value='Apply' />
     119</p>
     120
     121{%if action_message%}
     122<p>{{action_message}}</p>
     123{% if action_errors %}
     124<p>Errors:</p>
     125<table class='pretty-table'>
     126<tr>
     127    <th>ID</th>
     128    <th>Message</th>
     129{% for err in action_errors %}
     130<tr><th>{{err.0.pk}}</th><td>{{err.1}}</td></tr>
     131{%endfor%}
     132</ul>
     133{%endif%}
     134{%endif%}
     135</form>
     136
    79137{% endblock %}
  • remit/util/templatetags/misc.py

    re1086bd rdd6edfb  
    11from django import template
     2from django.template import Node, NodeList, Template, Context, Variable
    23import finance_core.models
    34
     
    3031    else: return 'zero'
    3132
     33class IfInNode(Node):
     34    def __init__(self, member, container, nodelist_true, nodelist_false, negate):
     35        self.member, self.container = member, container
     36        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     37        self.negate = negate
     38
     39    def __repr__(self):
     40        return "<IfInNode>"
     41
     42    def render(self, context):
     43        member_val = self.member.resolve(context, True)
     44        container_val = self.container.resolve(context, True)
     45        res = member_val in container_val
     46        if (self.negate and not res) or (not self.negate and res):
     47            return self.nodelist_true.render(context)
     48        return self.nodelist_false.render(context)
     49
     50def do_ifin(parser, token, negate):
     51    bits = list(token.split_contents())
     52    if len(bits) != 3:
     53        raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
     54    end_tag = 'end' + bits[0]
     55    nodelist_true = parser.parse(('else', end_tag))
     56    token = parser.next_token()
     57    if token.contents == 'else':
     58        nodelist_false = parser.parse((end_tag,))
     59        parser.delete_first_token()
     60    else:
     61        nodelist_false = NodeList()
     62    member = parser.compile_filter(bits[1])
     63    container = parser.compile_filter(bits[2])
     64    return IfInNode(member, container, nodelist_true, nodelist_false, negate)
     65register.tag("ifin", lambda x, y: do_ifin(x, y, False))
     66
     67
    3268register.filter('layer_num', finance_core.models.layer_num)
    3369register.filter('layer_name', finance_core.models.layer_name)
  • remit/vouchers/models.py

    rf52f909 r3e372da  
    33import finance_core
    44from finance_core.models import BudgetArea, BudgetTerm
     5
     6from django.core.mail import send_mail, mail_admins
     7from django.template import Context, Template
     8from django.template.loader import get_template
    59
    610import datetime
     
    5256        )
    5357
    54     def convert(self, signatory, signatory_email=settings.SIGNATORY_EMAIL):
     58    def convert(self, signatory, signatory_email=None):
     59        if signatory_email is None:
     60            signatory_email = settings.SIGNATORY_EMAIL
    5561        voucher = Voucher()
    5662        voucher.group_name = settings.GROUP_NAME
     
    8288        self.save()
    8389
     90    def approve(self, approver, signatory_name, signatory_email=None, ):
     91        """Mark a request as approved.
     92
     93        approver:       user object of the approving user
     94        signatory_name: name of signatory
     95        signatory_email: email address of signatory (provide None for default)
     96        """
     97        voucher = self.convert(signatory_name, signatory_email,)
     98        tmpl = get_template('vouchers/emails/request_approval_admin.txt')
     99        ctx = Context({
     100            'approver': approver,
     101            'request': self,
     102        })
     103        body = tmpl.render(ctx)
     104        mail_admins(
     105            'Request approval: %s approved $%s' % (
     106                approver,
     107                self.amount,
     108            ),
     109            body,
     110        )
     111
    84112    def label(self, ):
    85113        return settings.GROUP_ABBR + unicode(self.pk) + 'RR'
     
    222250    ),
    223251}
     252
     253class BulkRequestAction:
     254    def __init__(self, name, label, action, perm_predicate=None, ):
     255        self.name = name
     256        self.label = label
     257        self.action = action
     258        if perm_predicate is None:
     259            perm_predicate = lambda user: True
     260        elif perm_predicate == True:
     261            perm_predicate = lambda user: True
     262        self.perm_predicate = perm_predicate
     263    def can(self, user):
     264        return self.perm_predicate(user)
     265    def do(self, http_request, rr, ):
     266        if self.can(http_request.user):
     267            return self.action(http_request, rr, )
     268        else:
     269            return False, "permission denied"
     270    def __str__(self):
     271        return self.label
     272    @classmethod
     273    def filter_can_only(cls, actions, user):
     274        return [ action for action in actions if action.can(user) ]
     275def bulk_action_approve(http_request, rr):
     276    approver = http_request.user
     277    signatory_name = http_request.user.get_full_name()
     278    if rr.voucher:
     279        return False, "already approved"
     280    else:
     281        rr.approve(approver, signatory_name)
     282        return True, "request approved"
     283
     284def bulk_action_email_factory(stock_email_obj):
     285    assert stock_email_obj.context == 'request'
     286    def inner(http_request, rr):
     287        stock_email_obj.send_email_request(rr)
     288        return True, "mail sent"
     289    return inner
     290def perm_checker(perm):
     291    def predicate(user):
     292        return user.has_perm(perm)
     293    return predicate
     294
     295bulk_request_actions = []
     296if settings.SIGNATORY_EMAIL:
     297    bulk_request_actions.append(BulkRequestAction(
     298        name='approve',
     299        label='Approve Requests',
     300        action=bulk_action_approve,
     301        perm_predicate=perm_checker('vouchers.can_approve'),
     302    ))
     303for name, stockemail in stock_emails.items():
     304    if stockemail.context == 'request':
     305        bulk_request_actions.append(BulkRequestAction(
     306            name='email/%s' % name,
     307            label='Stock Email: %s' % stockemail.label,
     308            action=bulk_action_email_factory(stockemail),
     309            perm_predicate=perm_checker('vouchers.can_email'),
     310        ))
  • remit/vouchers/views.py

    r71ca9e6 r3e372da  
    257257            approve_form = VoucherizeForm(http_request.POST)
    258258            if approve_form.is_valid():
    259                 voucher = request_obj.convert(
    260                     approve_form.cleaned_data['name'],
    261                     signatory_email=approve_form.cleaned_data['email'],)
    262                 tmpl = get_template('vouchers/emails/request_approval_admin.txt')
    263                 ctx = Context({
    264                     'approver': http_request.user,
    265                     'request': request_obj,
    266                 })
    267                 body = tmpl.render(ctx)
    268                 mail_admins(
    269                     'Request approval: %s approved $%s' % (
    270                         http_request.user,
    271                         request_obj.amount,
    272                     ),
    273                     body,
     259                request_obj.approve(
     260                    approver=http_request.user,
     261                    signatory_name=approve_form.cleaned_data['name'],
     262                    signatory_email=approve_form.cleaned_data['email'],
    274263                )
    275264                approve_message = 'Created new voucher from request'
     
    365354)
    366355
     356def list_to_keys(lst):
     357    dct = {}
     358    for key in lst:
     359        dct[key] = True
     360    return dct
     361
    367362@login_required
    368 def show_requests(request, ):
    369     if request.user.has_perm('vouchers.can_list'):
     363def show_requests(http_request, ):
     364    # BULK ACTIONS
     365    actions = vouchers.models.BulkRequestAction.filter_can_only(
     366        vouchers.models.bulk_request_actions,
     367        http_request.user,
     368    )
     369    apply_action_message = None
     370    apply_action_errors = []
     371    if 'select' in http_request.REQUEST:
     372        selected_rr_ids = [ int(item) for item in http_request.REQUEST.getlist('select') ]
     373    else:
     374        selected_rr_ids = []
     375    if "apply-action" in http_request.POST:
     376        action_name = http_request.POST['action']
     377        if action_name == 'none':
     378            apply_action_message = "No action selected."
     379        else:
     380            matching_actions = [ action for action in actions if action.name == action_name]
     381            if(len(matching_actions) > 0):
     382                action = matching_actions[0]
     383                rrs = ReimbursementRequest.objects.filter(pk__in=selected_rr_ids)
     384                for rr in rrs:
     385                    success, msg = action.do(http_request, rr)
     386                    if not success:
     387                        apply_action_errors.append((rr, msg))
     388                apply_action_message = '"%s" applied to %d request(s) (%d errors encountered)' % (action.label, len(rrs), len(apply_action_errors), )
     389            else:
     390                apply_action_message = "Unknown or forbidden action requested."
     391
     392    # PERMISSION-BASED REQUEST FILTERING
     393    if http_request.user.has_perm('vouchers.can_list'):
    370394        qs = ReimbursementRequest.objects.all()
    371395        useronly = False
    372396    else:
    373         qs = ReimbursementRequest.objects.filter(get_related_requests_qobj(request.user))
     397        qs = ReimbursementRequest.objects.filter(get_related_requests_qobj(http_request.user))
    374398        useronly = True
    375399
    376     if 'order' in request.REQUEST:
    377         order_row = [row for row in request_list_orders if row[0] == request.REQUEST['order']]
     400    # SORTING
     401    if 'order' in http_request.REQUEST:
     402        order_row = [row for row in request_list_orders if row[0] == http_request.REQUEST['order']]
    378403        if order_row:
    379404            order, label, cols = order_row[0]
     
    384409        order = 'default'
    385410
    386     if 'approval_status' in request.REQUEST:
    387         approval_status = request.REQUEST['approval_status']
     411    # DISCRETIONARY REQUEST FILTERING
     412    if 'approval_status' in http_request.REQUEST:
     413        approval_status = http_request.REQUEST['approval_status']
    388414    else:
    389415        approval_status = vouchers.models.APPROVAL_STATE_PENDING
     
    401427            raise Http404('approval_status not known')
    402428
     429    # GENERATE THE REQUEST
    403430    return list_detail.object_list(
    404         request,
     431        http_request,
    405432        queryset=qs,
    406433        extra_context={
     434            'actions' : actions,
     435            'selected_ids'  : list_to_keys(selected_rr_ids),
     436            'action_message': apply_action_message,
     437            'action_errors' : apply_action_errors,
    407438            'useronly': useronly,
    408439            'order'   : order,
  • remit/util/add_gl_accounts.py

    r8132e8c r0df317c  
     1#!/usr/bin/python
     2
    13import sys
    24import os
     
    2527    ('Food.Meetings', 421000),
    2628    ('Food.Events', 421200),
    27     ('Computer Supplies', 421900),
     29    ('IT', None),
     30    ('IT.Computer Supplies', 421900),
     31    ('IT.On-line Services', 421920),
     32    ('Promotional & Memorabilia', 420302),
    2833)
    2934
Note: See TracChangeset for help on using the changeset viewer.