source: remit/vouchers/views.py @ f52f909

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

Allow sorting requests (Trac: #39)

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