import vouchers.models from vouchers.models import ReimbursementRequest, Documentation from finance_core.models import BudgetTerm, BudgetArea from util.shortcuts import get_403_response from django.contrib.auth.decorators import user_passes_test, login_required from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext from django.http import Http404, HttpResponseRedirect import django.forms from django.forms import Form from django.forms import ModelForm from django.forms import ModelChoiceField from django.core.urlresolvers import reverse from django.core.mail import send_mail, mail_admins from django.db.models import Q from django.template import Context, Template from django.template.loader import get_template import decimal import settings class RequestForm(ModelForm): class Meta: model = ReimbursementRequest fields = ( 'name', 'description', 'incurred_time', 'amount', 'budget_area', 'expense_area', 'check_to_first_name', 'check_to_last_name', 'check_to_email', 'check_to_addr', ) class CommitteesField(ModelChoiceField): def __init__(self, *args, **kargs): base_area = BudgetArea.get_by_path(settings.BASE_COMMITTEE_PATH) self.strip_levels = base_area.depth areas = (base_area.get_descendants() .filter(depth__lte=base_area.depth+settings.COMMITTEE_HIERARCHY_LEVELS) .exclude(name='Holding') ) ModelChoiceField.__init__(self, queryset=areas, help_text='Select the appropriate committe or other budget area', *args, **kargs) def label_from_instance(self, obj,): return obj.indented_name(strip_levels=self.strip_levels) class SelectRequestBasicsForm(Form): area = CommitteesField() term = ModelChoiceField(queryset = BudgetTerm.objects.all()) class DocUploadForm(ModelForm): def clean_backing_file(self, ): f = self.cleaned_data['backing_file'] ext = f.name.rsplit('.')[-1] contenttype = f.content_type if ext != 'pdf': raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a .%s file" % (ext, )) elif contenttype != 'application/pdf': raise django.forms.ValidationError("Only PDF files are accepted --- you submitted a %s file" % (contenttype, )) else: return f class Meta: model = Documentation fields = ( 'label', 'backing_file', ) @user_passes_test(lambda u: u.is_authenticated()) def select_request_basics(http_request, ): if http_request.method == 'POST': # If the form has been submitted... form = SelectRequestBasicsForm(http_request.POST) # A form bound to the POST data if form.is_valid(): # All validation rules pass term = form.cleaned_data['term'].slug area = form.cleaned_data['area'].id return HttpResponseRedirect(reverse(submit_request, args=[term, area],)) # Redirect after POST else: form = SelectRequestBasicsForm() # An unbound form context = { 'form':form, 'pagename':'request_reimbursement', } return render_to_response('vouchers/select.html', context, context_instance=RequestContext(http_request), ) class CommitteeBudgetAreasField(ModelChoiceField): def __init__(self, base_area, *args, **kargs): self.strip_levels = base_area.depth areas = base_area.get_descendants() ModelChoiceField.__init__(self, queryset=areas, help_text='In general, this should be a fully indented budget area, not one with children', *args, **kargs) def label_from_instance(self, obj,): return obj.indented_name(strip_levels=self.strip_levels) class ExpenseAreasField(ModelChoiceField): def __init__(self, *args, **kargs): base_area = vouchers.models.BudgetArea.get_by_path(['Accounts', 'Expenses']) self.strip_levels = base_area.depth areas = base_area.get_descendants() ModelChoiceField.__init__(self, queryset=areas, help_text='In general, this should be a fully indented budget area, not one with children', *args, **kargs) def label_from_instance(self, obj,): return obj.indented_name(strip_levels=self.strip_levels) @user_passes_test(lambda u: u.is_authenticated()) def submit_request(http_request, term, committee): term_obj = get_object_or_404(BudgetTerm, slug=term) comm_obj = get_object_or_404(BudgetArea, pk=committee) new_request = ReimbursementRequest() new_request.submitter = http_request.user.username new_request.budget_term = term_obj # Prefill from user information (itself prefilled from LDAP now) initial = {} initial['check_to_first_name'] = http_request.user.first_name initial['check_to_last_name'] = http_request.user.last_name initial['check_to_email'] = http_request.user.email if http_request.method == 'POST': # If the form has been submitted... form = RequestForm(http_request.POST, instance=new_request) # A form bound to the POST data form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj) form.fields['expense_area'] = ExpenseAreasField() if form.is_valid(): # All validation rules pass request_obj = form.save() # Send email tmpl = get_template('vouchers/emails/request_submit_admin.txt') ctx = Context({ 'submitter': http_request.user, 'request': request_obj, }) body = tmpl.render(ctx) recipients = [] for name, addr in settings.ADMINS: recipients.append(addr) recipients.append(request_obj.budget_area.owner_address()) if settings.CC_SUBMITTER: recipients.append(http_request.user.email) send_mail( '%sRequest submittal: %s requested $%s' % ( settings.EMAIL_SUBJECT_PREFIX, http_request.user, request_obj.amount, ), body, settings.SERVER_EMAIL, recipients, ) return HttpResponseRedirect(reverse(review_request, args=[new_request.pk],) + '?new=true') # Redirect after POST else: form = RequestForm(instance=new_request, initial=initial, ) # An unbound form form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj) form.fields['expense_area'] = ExpenseAreasField() context = { 'term':term_obj, 'comm':comm_obj, 'form':form, 'pagename':'request_reimbursement', } return render_to_response('vouchers/submit.html', context, context_instance=RequestContext(http_request), ) class VoucherizeForm(Form): name = django.forms.CharField(max_length=100, help_text='Signatory name for voucher',) email = django.forms.EmailField(max_length=100, help_text='Signatory email for voucher') @user_passes_test(lambda u: u.is_authenticated()) def review_request(http_request, object_id): request_obj = get_object_or_404(ReimbursementRequest, pk=object_id) user = http_request.user pagename = 'request_reimbursement' new = False if 'new' in http_request.REQUEST: if http_request.REQUEST['new'].upper() == 'TRUE': new = True else: new = False if (user.has_perm('vouchers.can_list') or user.username == request_obj.submitter or user.email.upper() == request_obj.check_to_email.upper() ): pass else: 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, ) # DOCUMENTATION # if request_obj.documentation: doc_upload_form = None else: new_docs = Documentation() new_docs.submitter = http_request.user.username if http_request.method == 'POST' and 'upload_documentation' in http_request.REQUEST: # If the form has been submitted... doc_upload_form = DocUploadForm(http_request.POST, http_request.FILES, instance=new_docs) # A form bound to the POST data if doc_upload_form.is_valid(): # All validation rules pass new_docs = doc_upload_form.save() request_obj.documentation = new_docs request_obj.save() return HttpResponseRedirect(reverse(review_request, args=[object_id],)) # Redirect after POST else: doc_upload_form = DocUploadForm(instance=new_docs, ) # An unbound form # SEND EMAILS show_email = http_request.user.has_perm('vouchers.can_email') if show_email: email_message = '' if http_request.method == 'POST' and 'send_email' in http_request.REQUEST: mail = vouchers.models.stock_emails[http_request.REQUEST['email_name']] assert mail.context == 'request' mail.send_email_request(request_obj) email_message = 'Sent email "%s".' % (mail.label, ) email_options = [] for mail in vouchers.models.stock_emails.values(): if mail.context == 'request': email_options.append({ 'label': mail.label, 'name' : mail.name, }) # APPROVE VOUCHERS show_approve = (http_request.user.has_perm('vouchers.can_approve') and request_obj.approval_status == vouchers.models.APPROVAL_STATE_PENDING) if show_approve: # Voucherize form # Prefill from certs / config initial = {} initial['name'] = '%s %s' % (http_request.user.first_name, http_request.user.last_name, ) if settings.SIGNATORY_EMAIL: initial['email'] = settings.SIGNATORY_EMAIL else: initial['email'] = http_request.user.email approve_message = '' if http_request.method == 'POST' and 'approve' in http_request.REQUEST: approve_form = VoucherizeForm(http_request.POST) if approve_form.is_valid(): request_obj.approve( approver=http_request.user, signatory_name=approve_form.cleaned_data['name'], signatory_email=approve_form.cleaned_data['email'], ) approve_message = 'Created new voucher from request' else: approve_form = VoucherizeForm(initial=initial) context = { 'rr':request_obj, 'pagename':pagename, 'new': new, 'doc_form': doc_upload_form, } if show_approve: context['approve_form'] = approve_form context['approve_message'] = approve_message if show_email: context['email_options'] = email_options context['email_message'] = email_message return render_to_response('vouchers/ReimbursementRequest_review.html', context, context_instance=RequestContext(http_request), ) @user_passes_test(lambda u: u.has_perm('vouchers.generate_vouchers')) def generate_vouchers(http_request, *args): unprocessed = True if 'unprocessed' in http_request.REQUEST: if http_request.REQUEST['unprocessed'].upper() == 'TRUE': unprocessed = True else: unprocessed = False mark = True if 'mark' in http_request.REQUEST: if http_request.REQUEST['mark'].upper() == 'TRUE': mark = True else: mark = False lst = vouchers.models.Voucher.objects.all() if unprocessed: lst = lst.filter(processed=False) total = decimal.Decimal('0.00') for voucher in lst: total = total + voucher.amount context = { 'vouchers': lst, 'total': total, 'MEDIA_ROOT': settings.MEDIA_ROOT, } response = render_to_response( 'vouchers/vouchers.tex', context, context_instance=RequestContext(http_request), mimetype=settings.LATEX_MIMETYPE, ) # Send mail tmpl = get_template('vouchers/emails/vouchers_tex.txt') ctx = Context({ 'converter': http_request.user, 'vouchers': lst, 'mark': mark, 'unprocessed': unprocessed, }) body = tmpl.render(ctx) mail_admins( 'Voucher rendering: %d by %s' % ( len(lst), http_request.user, ), body, ) if mark: for voucher in lst: voucher.mark_processed() return response def get_related_requests_qobj(user, ): return Q(submitter=user.username) | Q(check_to_email=user.email) request_list_orders = ( # Name Label Columns ('default', 'Default', ()), ('id', 'ID', ('id', )), ('state', 'Approval Status', ('approval_status', )), ('stateamount', 'Approval Status, then amount', ('approval_status', 'amount', )), ('stateto', 'Approval Status, then recipient', ('approval_status', 'check_to_first_name', 'check_to_last_name', )), ('statesubmit', 'Approval Status, then submitter', ('approval_status', 'submitter', )), ('name', 'Request Name', ('name', )), ('amount', 'Amount', ('amount', )), ('check_to', 'Check Recipient', ('check_to_first_name', 'check_to_last_name', )), ('submitter', 'Submitter', ('submitter', )), ) def list_to_keys(lst): dct = {} for key in lst: dct[key] = True return dct @login_required def show_requests(http_request, ): # BULK ACTIONS actions = vouchers.models.BulkRequestAction.filter_can_only( vouchers.models.bulk_request_actions, http_request.user, ) apply_action_message = None apply_action_errors = [] if 'select' in http_request.REQUEST: selected_rr_ids = [ int(item) for item in http_request.REQUEST.getlist('select') ] else: selected_rr_ids = [] if "apply-action" in http_request.POST: action_name = http_request.POST['action'] if action_name == 'none': apply_action_message = "No action selected." else: matching_actions = [ action for action in actions if action.name == action_name] if(len(matching_actions) > 0): action = matching_actions[0] rrs = ReimbursementRequest.objects.filter(pk__in=selected_rr_ids) for rr in rrs: success, msg = action.do(http_request, rr) if not success: apply_action_errors.append((rr, msg)) apply_action_message = '"%s" applied to %d request(s) (%d errors encountered)' % (action.label, len(rrs), len(apply_action_errors), ) else: apply_action_message = "Unknown or forbidden action requested." # PERMISSION-BASED REQUEST FILTERING if http_request.user.has_perm('vouchers.can_list'): qs = ReimbursementRequest.objects.all() useronly = False else: qs = ReimbursementRequest.objects.filter(get_related_requests_qobj(http_request.user)) useronly = True # SORTING if 'order' in http_request.REQUEST: order_row = [row for row in request_list_orders if row[0] == http_request.REQUEST['order']] if order_row: order, label, cols = order_row[0] qs = qs.order_by(*cols) else: raise Http404('Order by constraint not known') else: order = 'default' # DISCRETIONARY REQUEST FILTERING if 'approval_status' in http_request.REQUEST: approval_status = http_request.REQUEST['approval_status'] else: approval_status = vouchers.models.APPROVAL_STATE_PENDING if approval_status == 'all': pass else: try: approval_status = int(approval_status) except ValueError: raise Http404('approval_status poorly formatted') state_row = [row for row in vouchers.models.APPROVAL_STATES if row[0] == approval_status] if state_row: qs = qs.filter(approval_status=approval_status) else: raise Http404('approval_status not known') # GENERATE THE REQUEST context = { 'object_list' : qs, 'actions' : actions, 'selected_ids' : list_to_keys(selected_rr_ids), 'action_message': apply_action_message, 'action_errors' : apply_action_errors, 'useronly': useronly, 'order' : order, 'orders' : request_list_orders, 'approval_status' : approval_status, 'approval_states': vouchers.models.APPROVAL_STATES, 'pagename': 'list_requests', } return render_to_response('vouchers/reimbursementrequest_list.html', context, context_instance=RequestContext(http_request), )