source: remit/vouchers/views.py @ 5f82b6c

Last change on this file since 5f82b6c was 5f82b6c, checked in by Alex Dehnert <adehnert@…>, 11 years ago

Replace ListViewWithContext? with simpler render_to_response

It turns out that if you're specifying a queryset and a bunch of extra context,
ListView? isn't *actually* buying much...

  • Property mode set to 100644
File size: 16.8 KB
Line 
1import vouchers.models
2from vouchers.models import ReimbursementRequest, Documentation
3from finance_core.models import BudgetTerm, BudgetArea
4from util.shortcuts import get_403_response
5
6from django.contrib.auth.decorators import user_passes_test, login_required
7from django.shortcuts import render_to_response, get_object_or_404
8from django.template import RequestContext
9from django.http import Http404, HttpResponseRedirect
10import django.forms
11from django.forms import Form
12from django.forms import ModelForm
13from django.forms import ModelChoiceField
14from django.core.urlresolvers import reverse
15from django.core.mail import send_mail, mail_admins
16from django.db.models import Q
17from django.template import Context, Template
18from django.template.loader import get_template
19
20import decimal
21
22import settings
23
24class RequestForm(ModelForm):
25    class Meta:
26        model = ReimbursementRequest
27        fields = (
28            'name',
29            'description',
30            'incurred_time',
31            'amount',
32            'budget_area',
33            'expense_area',
34            'check_to_first_name',
35            'check_to_last_name',
36            'check_to_email',
37            'check_to_addr',
38        )
39
40
41class CommitteesField(ModelChoiceField):
42    def __init__(self, *args, **kargs):
43        base_area = BudgetArea.get_by_path(settings.BASE_COMMITTEE_PATH)
44        self.strip_levels = base_area.depth
45        areas = (base_area.get_descendants()
46            .filter(depth__lte=base_area.depth+settings.COMMITTEE_HIERARCHY_LEVELS)
47            .exclude(name='Holding')
48        )
49        ModelChoiceField.__init__(self, queryset=areas,
50            help_text='Select the appropriate committe or other budget area',
51            *args, **kargs)
52
53    def label_from_instance(self, obj,):
54        return obj.indented_name(strip_levels=self.strip_levels)
55
56class SelectRequestBasicsForm(Form):
57    area = CommitteesField()
58    term = ModelChoiceField(queryset = BudgetTerm.objects.all())
59
60class DocUploadForm(ModelForm):
61    def clean_backing_file(self, ):
62        f = self.cleaned_data['backing_file']
63        ext = f.name.rsplit('.')[-1]
64        contenttype = f.content_type
65        if ext != 'pdf':
66            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a .%s file" % (ext, ))
67        elif contenttype != 'application/pdf':
68            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a %s file" % (contenttype, ))
69        else:
70            return f
71
72    class Meta:
73        model = Documentation
74        fields = (
75            'label',
76            'backing_file',
77        )
78
79
80@user_passes_test(lambda u: u.is_authenticated())
81def select_request_basics(http_request, ):
82    if http_request.method == 'POST': # If the form has been submitted...
83        form = SelectRequestBasicsForm(http_request.POST) # A form bound to the POST data
84        if form.is_valid(): # All validation rules pass
85            term = form.cleaned_data['term'].slug
86            area = form.cleaned_data['area'].id
87            return HttpResponseRedirect(reverse(submit_request, args=[term, area],)) # Redirect after POST
88    else:
89        form = SelectRequestBasicsForm() # An unbound form
90
91    context = {
92        'form':form,
93        'pagename':'request_reimbursement',
94    }
95    return render_to_response('vouchers/select.html', context, context_instance=RequestContext(http_request), )
96
97class CommitteeBudgetAreasField(ModelChoiceField):
98    def __init__(self, base_area, *args, **kargs):
99        self.strip_levels = base_area.depth
100        areas = base_area.get_descendants()
101        ModelChoiceField.__init__(self, queryset=areas,
102            help_text='In general, this should be a fully indented budget area, not one with children',
103            *args, **kargs)
104
105    def label_from_instance(self, obj,):
106        return obj.indented_name(strip_levels=self.strip_levels)
107
108class ExpenseAreasField(ModelChoiceField):
109    def __init__(self, *args, **kargs):
110        base_area = vouchers.models.BudgetArea.get_by_path(['Accounts', 'Expenses'])
111        self.strip_levels = base_area.depth
112        areas = base_area.get_descendants()
113        ModelChoiceField.__init__(self, queryset=areas,
114            help_text='In general, this should be a fully indented budget area, not one with children',
115            *args, **kargs)
116
117    def label_from_instance(self, obj,):
118        return obj.indented_name(strip_levels=self.strip_levels)
119
120@user_passes_test(lambda u: u.is_authenticated())
121def submit_request(http_request, term, committee):
122    term_obj = get_object_or_404(BudgetTerm, slug=term)
123    comm_obj = get_object_or_404(BudgetArea, pk=committee)
124
125    new_request = ReimbursementRequest()
126    new_request.submitter = http_request.user.username
127    new_request.budget_term = term_obj
128
129    # Prefill from user information (itself prefilled from LDAP now)
130    initial = {}
131    initial['check_to_first_name'] = http_request.user.first_name
132    initial['check_to_last_name']  = http_request.user.last_name
133    initial['check_to_email']      = http_request.user.email
134
135    if http_request.method == 'POST': # If the form has been submitted...
136        form = RequestForm(http_request.POST, instance=new_request) # A form bound to the POST data
137        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
138        form.fields['expense_area'] = ExpenseAreasField()
139
140        if form.is_valid(): # All validation rules pass
141            request_obj = form.save()
142
143            # Send email
144            tmpl = get_template('vouchers/emails/request_submit_admin.txt')
145            ctx = Context({
146                'submitter': http_request.user,
147                'request': request_obj,
148            })
149            body = tmpl.render(ctx)
150            recipients = []
151            for name, addr in settings.ADMINS:
152                recipients.append(addr)
153            recipients.append(request_obj.budget_area.owner_address())
154            if settings.CC_SUBMITTER:
155                recipients.append(http_request.user.email)
156            send_mail(
157                '%sRequest submittal: %s requested $%s' % (
158                    settings.EMAIL_SUBJECT_PREFIX,
159                    http_request.user,
160                    request_obj.amount,
161                ),
162                body,
163                settings.SERVER_EMAIL,
164                recipients,
165            )
166
167            return HttpResponseRedirect(reverse(review_request, args=[new_request.pk],) + '?new=true') # Redirect after POST
168    else:
169        form = RequestForm(instance=new_request, initial=initial, ) # An unbound form
170        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
171        form.fields['expense_area'] = ExpenseAreasField()
172
173    context = {
174        'term':term_obj,
175        'comm':comm_obj,
176        'form':form,
177        'pagename':'request_reimbursement',
178    }
179    return render_to_response('vouchers/submit.html', context, context_instance=RequestContext(http_request), )
180
181class VoucherizeForm(Form):
182    name = django.forms.CharField(max_length=100, help_text='Signatory name for voucher',)
183    email = django.forms.EmailField(max_length=100, help_text='Signatory email for voucher')
184
185
186@user_passes_test(lambda u: u.is_authenticated())
187def review_request(http_request, object_id):
188    request_obj = get_object_or_404(ReimbursementRequest, pk=object_id)
189    user = http_request.user
190    pagename = 'request_reimbursement'
191    new = False
192    if 'new' in http_request.REQUEST:
193        if http_request.REQUEST['new'].upper() == 'TRUE':
194            new = True
195        else:
196            new = False
197
198    if (user.has_perm('vouchers.can_list') or
199        user.username == request_obj.submitter or
200        user.email.upper() == request_obj.check_to_email.upper()
201        ):
202        pass
203    else:
204        return get_403_response(http_request, errmsg="You do not have permission to access this reimbursement request. You can only view requests you submitted or are the recipient for, unless you have general viewing permissions.", pagename=pagename, )
205
206    # DOCUMENTATION #
207    if request_obj.documentation:
208        doc_upload_form = None
209    else:
210        new_docs = Documentation()
211        new_docs.submitter = http_request.user.username
212        if http_request.method == 'POST' and 'upload_documentation' in http_request.REQUEST: # If the form has been submitted...
213            doc_upload_form = DocUploadForm(http_request.POST, http_request.FILES, instance=new_docs) # A form bound to the POST data
214
215            if doc_upload_form.is_valid(): # All validation rules pass
216                new_docs = doc_upload_form.save()
217                request_obj.documentation = new_docs
218                request_obj.save()
219
220                return HttpResponseRedirect(reverse(review_request, args=[object_id],)) # Redirect after POST
221        else:
222            doc_upload_form = DocUploadForm(instance=new_docs, ) # An unbound form
223
224    # SEND EMAILS
225    show_email = http_request.user.has_perm('vouchers.can_email')
226    if show_email:
227        email_message = ''
228        if http_request.method == 'POST' and 'send_email' in http_request.REQUEST:
229            mail = vouchers.models.stock_emails[http_request.REQUEST['email_name']]
230            assert mail.context == 'request'
231            mail.send_email_request(request_obj)
232            email_message = 'Sent email "%s".' % (mail.label, )
233        email_options = []
234        for mail in vouchers.models.stock_emails.values():
235            if mail.context == 'request':
236                email_options.append({
237                    'label': mail.label,
238                    'name' : mail.name,
239                })
240
241    # APPROVE VOUCHERS
242    show_approve = (http_request.user.has_perm('vouchers.can_approve')
243        and request_obj.approval_status == vouchers.models.APPROVAL_STATE_PENDING)
244    if show_approve:
245        # Voucherize form
246        # Prefill from certs / config
247        initial = {}
248        initial['name'] = '%s %s' % (http_request.user.first_name, http_request.user.last_name, )
249        if settings.SIGNATORY_EMAIL:
250            initial['email'] = settings.SIGNATORY_EMAIL
251        else:
252            initial['email'] = http_request.user.email
253
254        approve_message = ''
255        if http_request.method == 'POST' and 'approve' in http_request.REQUEST:
256            approve_form = VoucherizeForm(http_request.POST)
257            if approve_form.is_valid():
258                request_obj.approve(
259                    approver=http_request.user,
260                    signatory_name=approve_form.cleaned_data['name'],
261                    signatory_email=approve_form.cleaned_data['email'],
262                )
263                approve_message = 'Created new voucher from request'
264        else:
265            approve_form = VoucherizeForm(initial=initial)
266
267    context = {
268        'rr':request_obj,
269        'pagename':pagename,
270        'new': new,
271        'doc_form': doc_upload_form,
272    }
273    if show_approve:
274        context['approve_form'] = approve_form
275        context['approve_message'] = approve_message
276    if show_email:
277        context['email_options'] = email_options
278        context['email_message'] = email_message
279    return render_to_response('vouchers/ReimbursementRequest_review.html', context, context_instance=RequestContext(http_request), )
280
281@user_passes_test(lambda u: u.has_perm('vouchers.generate_vouchers'))
282def generate_vouchers(http_request, *args):
283    unprocessed = True
284    if 'unprocessed' in http_request.REQUEST:
285        if http_request.REQUEST['unprocessed'].upper() == 'TRUE':
286            unprocessed = True
287        else:
288            unprocessed = False
289    mark = True
290    if 'mark' in http_request.REQUEST:
291        if http_request.REQUEST['mark'].upper() == 'TRUE':
292            mark = True
293        else:
294            mark = False
295
296    lst = vouchers.models.Voucher.objects.all()
297    if unprocessed:
298        lst = lst.filter(processed=False)
299
300    total = decimal.Decimal('0.00')
301    for voucher in lst:
302        total = total + voucher.amount
303
304    context = {
305        'vouchers': lst,
306        'total': total,
307        'MEDIA_ROOT': settings.MEDIA_ROOT,
308    }
309    response = render_to_response(
310        'vouchers/vouchers.tex',
311        context, context_instance=RequestContext(http_request),
312        mimetype=settings.LATEX_MIMETYPE,
313    )
314
315    # Send mail
316    tmpl = get_template('vouchers/emails/vouchers_tex.txt')
317    ctx = Context({
318        'converter': http_request.user,
319        'vouchers': lst,
320        'mark': mark,
321        'unprocessed': unprocessed,
322    })
323    body = tmpl.render(ctx)
324    mail_admins(
325        'Voucher rendering: %d by %s' % (
326            len(lst),
327            http_request.user,
328        ),
329        body,
330    )
331
332    if mark:
333        for voucher in lst:
334            voucher.mark_processed()
335
336    return response
337
338def get_related_requests_qobj(user, ):
339    return Q(submitter=user.username) | Q(check_to_email=user.email)
340
341request_list_orders = (
342#   Name            Label               Columns
343    ('default',     'Default',          ()),
344    ('id',          'ID',               ('id', )),
345    ('state',       'Approval Status',  ('approval_status', )),
346    ('stateamount', 'Approval Status, then amount',  ('approval_status', 'amount', )),
347    ('stateto',     'Approval Status, then recipient',  ('approval_status', 'check_to_first_name', 'check_to_last_name', )),
348    ('statesubmit', 'Approval Status, then submitter',  ('approval_status', 'submitter', )),
349    ('name',        'Request Name',     ('name', )),
350    ('amount',      'Amount',           ('amount', )),
351    ('check_to',    'Check Recipient',  ('check_to_first_name', 'check_to_last_name', )),
352    ('submitter',   'Submitter',        ('submitter', )),
353)
354
355def list_to_keys(lst):
356    dct = {}
357    for key in lst:
358        dct[key] = True
359    return dct
360
361@login_required
362def show_requests(http_request, ):
363    # BULK ACTIONS
364    actions = vouchers.models.BulkRequestAction.filter_can_only(
365        vouchers.models.bulk_request_actions,
366        http_request.user,
367    )
368    apply_action_message = None
369    apply_action_errors = []
370    if 'select' in http_request.REQUEST:
371        selected_rr_ids = [ int(item) for item in http_request.REQUEST.getlist('select') ]
372    else:
373        selected_rr_ids = []
374    if "apply-action" in http_request.POST:
375        action_name = http_request.POST['action']
376        if action_name == 'none':
377            apply_action_message = "No action selected."
378        else:
379            matching_actions = [ action for action in actions if action.name == action_name]
380            if(len(matching_actions) > 0):
381                action = matching_actions[0]
382                rrs = ReimbursementRequest.objects.filter(pk__in=selected_rr_ids)
383                for rr in rrs:
384                    success, msg = action.do(http_request, rr)
385                    if not success:
386                        apply_action_errors.append((rr, msg))
387                apply_action_message = '"%s" applied to %d request(s) (%d errors encountered)' % (action.label, len(rrs), len(apply_action_errors), )
388            else:
389                apply_action_message = "Unknown or forbidden action requested."
390
391    # PERMISSION-BASED REQUEST FILTERING
392    if http_request.user.has_perm('vouchers.can_list'):
393        qs = ReimbursementRequest.objects.all()
394        useronly = False
395    else:
396        qs = ReimbursementRequest.objects.filter(get_related_requests_qobj(http_request.user))
397        useronly = True
398
399    # SORTING
400    if 'order' in http_request.REQUEST:
401        order_row = [row for row in request_list_orders if row[0] == http_request.REQUEST['order']]
402        if order_row:
403            order, label, cols = order_row[0]
404            qs = qs.order_by(*cols)
405        else:
406            raise Http404('Order by constraint not known')
407    else:
408        order = 'default'
409
410    # DISCRETIONARY REQUEST FILTERING
411    if 'approval_status' in http_request.REQUEST:
412        approval_status = http_request.REQUEST['approval_status']
413    else:
414        approval_status = vouchers.models.APPROVAL_STATE_PENDING
415    if approval_status == 'all':
416        pass
417    else:
418        try:
419            approval_status = int(approval_status)
420        except ValueError:
421            raise Http404('approval_status poorly formatted')
422        state_row = [row for row in vouchers.models.APPROVAL_STATES if row[0] == approval_status]
423        if state_row:
424            qs = qs.filter(approval_status=approval_status)
425        else:
426            raise Http404('approval_status not known')
427
428    # GENERATE THE REQUEST
429
430    context = {
431        'object_list' : qs,
432        'actions' : actions,
433        'selected_ids'  : list_to_keys(selected_rr_ids),
434        'action_message': apply_action_message,
435        'action_errors' : apply_action_errors,
436        'useronly': useronly,
437        'order'   : order,
438        'orders'  : request_list_orders,
439        'approval_status' : approval_status,
440        'approval_states':  vouchers.models.APPROVAL_STATES,
441        'pagename': 'list_requests',
442    }
443    return render_to_response('vouchers/reimbursementrequest_list.html', context, context_instance=RequestContext(http_request), )
Note: See TracBrowser for help on using the repository browser.