source: remit/vouchers/views.py @ 6f24604

client
Last change on this file since 6f24604 was 6f24604, checked in by Alex Dehnert <adehnert@…>, 15 years ago

Make reimbursement base path configurable

Adds a BASE_COMMITTEE_PATH parameter (default ['Accounts', 'Assets', ])
to use as the base path for reimbursement requests.

UPGRADE NOTE: Previously the value was hardcoded as
['Accounts', 'Assets', 'Budget', ] --- previous deployments should make
sure to set BASE_COMMITTEE_PATH to that when upgrading.

  • Property mode set to 100644
File size: 12.6 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
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.template import Context, Template
17from django.template.loader import get_template
18
19import settings
20
21class RequestForm(ModelForm):
22    class Meta:
23        model = ReimbursementRequest
24        fields = (
25            'name',
26            'description',
27            'amount',
28            'budget_area',
29            'expense_area',
30            'check_to_first_name',
31            'check_to_last_name',
32            'check_to_email',
33            'check_to_addr',
34        )
35
36class CommitteesField(ModelChoiceField):
37    def __init__(self, *args, **kargs):
38        base_area = BudgetArea.get_by_path(settings.BASE_COMMITTEE_PATH)
39        self.strip_levels = base_area.depth
40        areas = (base_area.get_descendants()
41            .filter(depth__lte=base_area.depth+2)
42            .exclude(name='Holding')
43        )
44        ModelChoiceField.__init__(self, queryset=areas,
45            help_text='Select the appropriate committe or other budget area',
46            *args, **kargs)
47
48    def label_from_instance(self, obj,):
49        return obj.indented_name(strip_levels=self.strip_levels)
50
51class SelectRequestBasicsForm(Form):
52    area = CommitteesField()
53    term = ModelChoiceField(queryset = BudgetTerm.objects.all())
54
55class DocUploadForm(ModelForm):
56    def clean_backing_file(self, ):
57        f = self.cleaned_data['backing_file']
58        ext = f.name.rsplit('.')[-1]
59        contenttype = f.content_type
60        if ext != 'pdf':
61            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a .%s file" % (ext, ))
62        elif contenttype != 'application/pdf':
63            raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a %s file" % (contenttype, ))
64        else:
65            return f
66
67    class Meta:
68        model = Documentation
69        fields = (
70            'label',
71            'backing_file',
72        )
73
74
75@user_passes_test(lambda u: u.is_authenticated())
76def select_request_basics(http_request, ):
77    if http_request.method == 'POST': # If the form has been submitted...
78        form = SelectRequestBasicsForm(http_request.POST) # A form bound to the POST data
79        if form.is_valid(): # All validation rules pass
80            term = form.cleaned_data['term'].slug
81            area = form.cleaned_data['area'].id
82            return HttpResponseRedirect(reverse(submit_request, args=[term, area],)) # Redirect after POST
83    else:
84        form = SelectRequestBasicsForm() # An unbound form
85
86    context = {
87        'form':form,
88        'pagename':'request_reimbursement',
89    }
90    return render_to_response('vouchers/select.html', context, context_instance=RequestContext(http_request), )
91
92class CommitteeBudgetAreasField(ModelChoiceField):
93    def __init__(self, base_area, *args, **kargs):
94        self.strip_levels = base_area.depth
95        areas = base_area.get_descendants()
96        ModelChoiceField.__init__(self, queryset=areas,
97            help_text='In general, this should be a fully indented budget area, not one with children',
98            *args, **kargs)
99
100    def label_from_instance(self, obj,):
101        return obj.indented_name(strip_levels=self.strip_levels)
102
103class ExpenseAreasField(ModelChoiceField):
104    def __init__(self, *args, **kargs):
105        base_area = vouchers.models.BudgetArea.get_by_path(['Accounts', 'Expenses'])
106        self.strip_levels = base_area.depth
107        areas = base_area.get_descendants()
108        ModelChoiceField.__init__(self, queryset=areas,
109            help_text='In general, this should be a fully indented budget area, not one with children',
110            *args, **kargs)
111
112    def label_from_instance(self, obj,):
113        return obj.indented_name(strip_levels=self.strip_levels)
114
115@user_passes_test(lambda u: u.is_authenticated())
116def submit_request(http_request, term, committee):
117    term_obj = get_object_or_404(BudgetTerm, slug=term)
118    comm_obj = get_object_or_404(BudgetArea, pk=committee)
119
120    new_request = ReimbursementRequest()
121    new_request.submitter = http_request.user.username
122    new_request.budget_term = term_obj
123
124    # Prefill from user information (itself prefilled from LDAP now)
125    initial = {}
126    initial['check_to_first_name'] = http_request.user.first_name
127    initial['check_to_last_name']  = http_request.user.last_name
128    initial['check_to_email']      = http_request.user.email
129
130    if http_request.method == 'POST': # If the form has been submitted...
131        form = RequestForm(http_request.POST, instance=new_request) # A form bound to the POST data
132        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
133        form.fields['expense_area'] = ExpenseAreasField()
134
135        if form.is_valid(): # All validation rules pass
136            request_obj = form.save()
137
138            # Send email
139            tmpl = get_template('vouchers/emails/request_submit_admin.txt')
140            ctx = Context({
141                'submitter': http_request.user,
142                'request': request_obj,
143            })
144            body = tmpl.render(ctx)
145            recipients = []
146            for name, addr in settings.ADMINS:
147                recipients.append(addr)
148            recipients.append(request_obj.budget_area.owner_address())
149            send_mail(
150                '%sRequest submittal: %s requested $%s' % (
151                    settings.EMAIL_SUBJECT_PREFIX,
152                    http_request.user,
153                    request_obj.amount,
154                ),
155                body,
156                settings.SERVER_EMAIL,
157                recipients,
158            )
159
160            return HttpResponseRedirect(reverse(review_request, args=[new_request.pk],) + '?new=true') # Redirect after POST
161    else:
162        form = RequestForm(instance=new_request, initial=initial, ) # An unbound form
163        form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj)
164        form.fields['expense_area'] = ExpenseAreasField()
165
166    context = {
167        'term':term_obj,
168        'comm':comm_obj,
169        'form':form,
170        'pagename':'request_reimbursement',
171    }
172    return render_to_response('vouchers/submit.html', context, context_instance=RequestContext(http_request), )
173
174class VoucherizeForm(Form):
175    name = django.forms.CharField(max_length=100, help_text='Signatory name for voucher',)
176    email = django.forms.EmailField(max_length=100, help_text='Signatory email for voucher')
177
178
179@user_passes_test(lambda u: u.is_authenticated())
180def review_request(http_request, object_id):
181    request_obj = get_object_or_404(ReimbursementRequest, pk=object_id)
182    user = http_request.user
183    pagename = 'request_reimbursement'
184    new = False
185    if 'new' in http_request.REQUEST:
186        if http_request.REQUEST['new'].upper() == 'TRUE':
187            new = True
188        else:
189            new = False
190
191    if (user.has_perm('vouchers.view_requests') or
192        user.username == request_obj.submitter or
193        user.email.upper() == request_obj.check_to_email.upper()
194        ):
195        pass
196    else:
197        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, )
198
199    # DOCUMENTATION #
200    if request_obj.documentation:
201        doc_upload_form = None
202    else:
203        new_docs = Documentation()
204        new_docs.submitter = http_request.user.username
205        if http_request.method == 'POST' and 'upload_documentation' in http_request.REQUEST: # If the form has been submitted...
206            doc_upload_form = DocUploadForm(http_request.POST, http_request.FILES, instance=new_docs) # A form bound to the POST data
207
208            if doc_upload_form.is_valid(): # All validation rules pass
209                new_docs = doc_upload_form.save()
210                request_obj.documentation = new_docs
211                request_obj.save()
212
213                return HttpResponseRedirect(reverse(review_request, args=[object_id],)) # Redirect after POST
214        else:
215            doc_upload_form = DocUploadForm(instance=new_docs, ) # An unbound form
216
217    # SEND EMAILS
218    show_email = http_request.user.has_perm('vouchers.can_email')
219    if show_email:
220        email_message = ''
221        if http_request.method == 'POST' and 'send_email' in http_request.REQUEST:
222            mail = vouchers.models.stock_emails[http_request.REQUEST['email_name']]
223            assert mail.context == 'request'
224            mail.send_email_request(request_obj)
225            email_message = 'Sent email "%s".' % (mail.label, )
226        email_options = []
227        for mail in vouchers.models.stock_emails.values():
228            if mail.context == 'request':
229                email_options.append({
230                    'label': mail.label,
231                    'name' : mail.name,
232                })
233
234    # APPROVE VOUCHERS
235    show_approve = (http_request.user.has_perm('vouchers.can_approve')
236        and request_obj.approval_status == vouchers.models.APPROVAL_STATE_PENDING)
237    if show_approve:
238        # Voucherize form
239        # Prefill from certs / config
240        initial = {}
241        initial['name'] = '%s %s' % (http_request.user.first_name, http_request.user.last_name, )
242        if settings.SIGNATORY_EMAIL:
243            initial['email'] = settings.SIGNATORY_EMAIL
244        else:
245            initial['email'] = http_request.user.email
246
247        approve_message = ''
248        if http_request.method == 'POST' and 'approve' in http_request.REQUEST:
249            approve_form = VoucherizeForm(http_request.POST)
250            if approve_form.is_valid():
251                voucher = request_obj.convert(
252                    approve_form.cleaned_data['name'],
253                    signatory_email=approve_form.cleaned_data['email'],)
254                tmpl = get_template('vouchers/emails/request_approval_admin.txt')
255                ctx = Context({
256                    'approver': http_request.user,
257                    'request': request_obj,
258                })
259                body = tmpl.render(ctx)
260                mail_admins(
261                    'Request approval: %s approved $%s' % (
262                        http_request.user,
263                        request_obj.amount,
264                    ),
265                    body,
266                )
267                approve_message = 'Created new voucher from request'
268        else:
269            approve_form = VoucherizeForm(initial=initial)
270
271    context = {
272        'rr':request_obj,
273        'pagename':pagename,
274        'new': new,
275        'doc_form': doc_upload_form,
276    }
277    if show_approve:
278        context['approve_form'] = approve_form
279        context['approve_message'] = approve_message
280    if show_email:
281        context['email_options'] = email_options
282        context['email_message'] = email_message
283    return render_to_response('vouchers/ReimbursementRequest_review.html', context, context_instance=RequestContext(http_request), )
284
285@user_passes_test(lambda u: u.has_perm('vouchers.generate_vouchers'))
286def generate_vouchers(http_request, *args):
287    unprocessed = True
288    if 'unprocessed' in http_request.REQUEST:
289        if http_request.REQUEST['unprocessed'].upper() == 'TRUE':
290            unprocessed = True
291        else:
292            unprocessed = False
293    mark = True
294    if 'mark' in http_request.REQUEST:
295        if http_request.REQUEST['mark'].upper() == 'TRUE':
296            mark = True
297        else:
298            mark = False
299
300    lst = vouchers.models.Voucher.objects.all()
301    if unprocessed:
302        lst = lst.filter(processed=False)
303
304    context = {
305        'vouchers': lst,
306        'MEDIA_ROOT': settings.MEDIA_ROOT,
307    }
308    response = render_to_response('vouchers/vouchers.tex', context, context_instance=RequestContext(http_request), )
309
310    # Send mail
311    tmpl = get_template('vouchers/emails/vouchers_tex.txt')
312    ctx = Context({
313        'converter': http_request.user,
314        'vouchers': lst,
315        'mark': mark,
316        'unprocessed': unprocessed,
317    })
318    body = tmpl.render(ctx)
319    mail_admins(
320        'Voucher rendering: %d by %s' % (
321            len(lst),
322            http_request.user,
323        ),
324        body,
325    )
326
327    if mark:
328        for voucher in lst:
329            voucher.processed = True
330            voucher.save()
331
332    return response
Note: See TracBrowser for help on using the repository browser.