Changeset 45a52c6
- Timestamp:
- Jul 14, 2014, 1:21:16 AM (10 years ago)
- Branches:
- master
- Parents:
- 4439c52 (diff), 3b7f125 (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@…> (07/14/14 01:21:16)
- git-committer:
- Alex Dehnert <adehnert@…> (07/14/14 01:21:16)
- Files:
-
- 8 added
- 10 edited
Legend:
- Unmodified
- Added
- Removed
-
INSTALL.rst
rcabf7ac r4f2c77a 39 39 - ``ADMINS`` 40 40 - Database configuration 41 - ``SERVER_EMAIL`` 41 - ``SERVER_EMAIL`` and ``DEFAULT_FROM_EMAIL`` 42 42 - ``SITE_URL_BASE`` -- full URL (including protocol and hostname; used in emails) 43 43 - Any other settings you want … … 53 53 7. Use the admin interface to set up appropriate BudgetTerm(s) 54 54 8. Make sure that the remit/media/ directory gets served to the web as media 55 56 Post-setup notes 57 ---------------- 58 59 - By default, the "downloader" user is inactive and has no password. To use it, you will need to make it active, set a password, and configure your client (see ``client/README`` for details). -
client/downloader.py
rf8a5e4b r952d9fb 1 #!/usr/bin/ python1 #!/usr/bin/env python 2 2 3 3 import os … … 36 36 37 37 def getLaTeX(br, latex_file, ): 38 br.open(baseurl + 'vouchers/generate/ ')38 br.open(baseurl + 'vouchers/generate/vouchers.tex') 39 39 if br.viewing_html(): 40 40 print br.response().get_data() -
remit/remit_templates/vouchers/ReimbursementRequest_review.html
r0445831 r03aae73 86 86 87 87 {% if doc_form %} 88 <h3> (Optional)Upload Documentation</h3>88 <h3>Upload Documentation</h3> 89 89 90 <p> If you ordered online, you may wish to upload documentation instead of providing a physical copy.</p>90 <p>SAO requires that all RFPs be submitted with electronic documentation. If you ordered online, you can just upload a PDF of your purchase details. For in-person purchases, you should upload a scan of the receipt. Be sure documentation includes both itemization and proof of payment.</p> 91 91 92 92 <form enctype="multipart/form-data" method="post" action=""> … … 120 120 {% endif %} 121 121 122 {% if approve_form %}123 <h3>Approve this reimbursement request:</h3>124 {% if approve_message %}125 <p>{{ approve_message }}</p>126 {% else %}127 {% if perms.vouchers.change_reimbursementrequest %}128 <p>Optionally, <a href='{% url "admin:vouchers_reimbursementrequest_change" rr.pk %}'>change this request first</a>.</p>129 {% endif %}130 <form method="post" action="">131 <table class='pretty-table'>132 {{ approve_form.as_table }}133 122 134 <tr> 135 <th colspan='2'><input type="submit" name="approve" value="Approve" /></td> 136 </tr> 137 </table> 138 {% csrf_token %} 139 </form> 140 {% endif %} 123 {% if show_approve or approve_message %} 124 <h3>Approve this reimbursement request:</h3> 125 {% if approve_message %} 126 <div class='messagebox {{approve_level}}box'> 127 <p>{{approve_message}}</p> 128 </div> 129 {% else %} 130 {% if perms.vouchers.change_reimbursementrequest %} 131 <p>Optionally, <a href='{% url "admin:vouchers_reimbursementrequest_change" rr.pk %}'>change this request first</a>.</p> 132 {% endif %} 133 <form method="post" action="."> 134 <th colspan='2'><input type="submit" name="approve" value="Approve as RFP" /></td> 135 {% csrf_token %} 136 </form> 137 {% endif %} 141 138 {% endif %} 142 139 -
remit/remit_templates/vouchers/submit.html
rf301a50 r2b31097 19 19 <td>{{comm}}</td> 20 20 </tr> 21 <tr> 22 <th>Recipient type</th> 23 <td>{{request.get_recipient_type_display}}</td> 24 </tr> 21 25 22 26 <tr class='sect-head'> -
remit/settings/__init__.py
r7a292ed rfeed77c 16 16 ) 17 17 SERVER_EMAIL = 'remit-default-addr@mit.edu' 18 DEFAULT_FROM_EMAIL = 'remit-default-addr@mit.edu' 18 19 19 20 GROUP_NAME = 'Remit' 20 21 GROUP_ABBR = 'RM' 21 SIGNATORY_EMAIL = None 22 SIGNATORY_EMAIL = None # used only for paper vouchers (which SAO killed) 22 23 23 24 CC_SUBMITTER = False -
remit/vouchers/admin.py
r9102ac9 rfeed77c 27 27 list_display_links = ('description', ) 28 28 29 class RFPAdmin(admin.ModelAdmin): 30 list_display = ('pk', 'name', 'rfp_number', 'rfp_submit_time', 'payee_type', 'payee_name', 'item_amount', 'item_co', 'item_gl', 'documentation', ) 31 list_display_links = ('pk', 'name', ) 32 29 33 admin.site.register(vouchers.models.ReimbursementRequest, ReimbursementRequestAdmin) 30 34 admin.site.register(vouchers.models.Voucher, VoucherAdmin) 35 admin.site.register(vouchers.models.RFP, RFPAdmin) -
remit/vouchers/models.py
r42b287c rfeed77c 10 10 11 11 import datetime 12 import re 12 13 13 14 APPROVAL_STATE_PENDING = 0 … … 20 21 ) 21 22 23 recipient_type_choices = ( 24 ('mit', 'MIT student, faculty, or staff', ), 25 ('other', 'Other, including alumnus or affiliate', ), 26 ) 22 27 class ReimbursementRequest(models.Model): 23 28 submitter = models.CharField(max_length=30) # Username of submitter 29 recipient_type = models.CharField(choices=recipient_type_choices, max_length=10, default='other') 24 30 check_to_first_name = models.CharField(max_length=50, verbose_name="check recipient's first name") 25 31 check_to_last_name = models.CharField(max_length=50, verbose_name="check recipient's last name") … … 95 101 def convert_to_rfp(self, ): 96 102 rfp = RFP() 103 max_name_len = RFP._meta.get_field_by_name('name')[0].max_length 104 rfp.name = ("%s: %s" % (self.label(), self.name))[:max_name_len] 105 rfp.payee_type = self.recipient_type 106 rfp.payee_name = "%s %s" % (self.check_to_first_name, self.check_to_last_name) 107 rfp.fill_addr(self.check_to_addr) 108 rfp.item_date = self.incurred_time.date() 109 rfp.item_gl = self.expense_area.get_account_number() 110 rfp.item_co = self.budget_area.get_account_number() 111 rfp.item_amount = self.amount 112 rfp.item_desc = self.description 113 rfp.documentation = self.documentation 97 114 rfp.save() 98 115 self.create_transfers() … … 102 119 self.save() 103 120 104 def approve(self, approver, signatory_name, signatory_email=None, ): 121 def send_approval_email(self, approver, approval_type): 122 tmpl = get_template('vouchers/emails/request_approval_admin.txt') 123 ctx = Context({ 124 'approver': approver, 125 'request': self, 126 'approval_type': approval_type, 127 }) 128 body = tmpl.render(ctx) 129 mail_admins( 130 'Request approval: %s approved $%s [%s]' % ( 131 approver, 132 self.amount, 133 approval_type, 134 ), 135 body, 136 ) 137 138 def approve_as_voucher(self, approver, signatory_name, signatory_email=None, ): 105 139 """Mark a request as approved. 106 140 … … 110 144 """ 111 145 self.convert_to_voucher(signatory_name, signatory_email,) 112 tmpl = get_template('vouchers/emails/request_approval_admin.txt') 113 ctx = Context({ 114 'approver': approver, 115 'request': self, 116 'approval_type': 'voucher', 117 }) 118 body = tmpl.render(ctx) 119 mail_admins( 120 'Request approval: %s approved $%s [voucher]' % ( 121 approver, 122 self.amount, 123 ), 124 body, 125 ) 146 self.send_approval_email(approver, 'voucher') 126 147 127 148 def approve_as_rfp(self, approver, ): 128 149 self.convert_to_rfp() 129 tmpl = get_template('vouchers/emails/request_approval_admin.txt') 130 ctx = Context({ 131 'approver': approver, 132 'request': self, 133 'approval_type': 'RFP', 134 }) 135 body = tmpl.render(ctx) 136 mail_admins( 137 'Request approval: %s approved $%s [RFP]' % ( 138 approver, 139 self.amount, 140 ), 141 body, 142 ) 143 150 self.send_approval_email(approver, 'RFP') 144 151 145 152 def review_link(self, ): … … 162 169 description = models.TextField() 163 170 gl = models.IntegerField() 164 processed = models.BooleanField( )171 processed = models.BooleanField(default=False) 165 172 process_time = models.DateTimeField(blank=True, null=True,) 166 documentation = models.ForeignKey('Documentation', blank=True,null=True, )173 documentation = models.ForeignKey('Documentation', null=True, ) 167 174 168 175 def mailing_addr_lines(self): … … 196 203 197 204 class RFP(models.Model): 205 # Class constants 206 addr_regex = re.compile(r'^(?P<street>.+)\n(?P<city>.+), (?P<state>\w\w) (?P<zip>\d{5}(-\d{4})?)$') 207 addr_error = 'Address must two lines: a street address, followed by "<city>, <two letter state abbreviation> <zip>". For non-US addresses, please contact a Treasurer for help.' 208 209 # Fields 198 210 create_time = models.DateTimeField(default=datetime.datetime.now) 211 rfp_submit_time = models.DateTimeField(default=None, null=True, blank=True) 212 rfp_number = models.IntegerField(default=None, null=True, blank=True) 213 214 name = models.CharField(max_length=25, verbose_name='short description', ) 215 payee_type = models.CharField(choices=recipient_type_choices, max_length=10, default='other') 216 payee_name = models.CharField(max_length=35, ) 217 addr_street = models.CharField(max_length=35, verbose_name='street address', blank=True, ) 218 addr_city = models.CharField(max_length=35, verbose_name='city', blank=True, ) 219 addr_state = models.CharField(max_length=10, verbose_name='state', blank=True, ) 220 addr_zip = models.CharField(max_length=10, verbose_name='zipcode', blank=True, ) 221 item_date = models.DateField() 222 item_gl = models.CharField(max_length=10, verbose_name="item's G/L account") 223 item_co = models.CharField(max_length=12, verbose_name="item's cost object") 224 item_amount = models.DecimalField(max_digits=7, decimal_places=2) 225 item_desc = models.TextField() 226 documentation = models.ForeignKey('Documentation', blank=True, null=True, ) 227 228 def fill_addr(self, address): 229 if address == '': 230 return 231 match = self.addr_regex.match(address) 232 assert match != None 233 self.addr_street = match.group("street") 234 self.addr_city = match.group("city") 235 self.addr_state = match.group("state") 236 self.addr_zip = match.group("zip") 199 237 200 238 def __unicode__(self, ): 201 239 return "RFP: %s" % (self.create_time.strftime(settings.SHORT_DATETIME_FORMAT_F), ) 240 241 class Meta: 242 verbose_name = "RFP" 202 243 203 244 class Documentation(models.Model): … … 316 357 return [ action for action in actions if action.can(user) ] 317 358 318 def bulk_action_approve (http_request, rr):359 def bulk_action_approve_as_voucher(http_request, rr): 319 360 approver = http_request.user 320 361 signatory_name = http_request.user.get_full_name() … … 322 363 return False, "already approved" 323 364 else: 324 rr.approve (approver, signatory_name)365 rr.approve_as_voucher(approver, signatory_name) 325 366 return True, "request approved" 326 367 … … 347 388 if settings.SIGNATORY_EMAIL: 348 389 bulk_request_actions.append(BulkRequestAction( 349 name='approve ',350 label='Approve Requests',351 action=bulk_action_approve ,390 name='approve_as_voucher', 391 label='Approve requests (for vouchers)', 392 action=bulk_action_approve_as_voucher, 352 393 perm_predicate=perm_checker('vouchers.can_approve'), 353 394 )) 354 395 bulk_request_actions.append(BulkRequestAction( 355 396 name='approve_as_rfp', 356 label=' Mark Requests as RFPized',397 label='Approve requests (for RFPizing)', 357 398 action=bulk_action_approve_as_rfp, 358 399 perm_predicate=perm_checker('vouchers.can_approve'), -
remit/vouchers/urls.py
rff623c3 rfeed77c 7 7 url(r'list/', vouchers.views.show_requests, name='list_requests', ), 8 8 url(r'reimbursement/', 'vouchers.views.select_request_basics', name='request_reimbursement', ), 9 (r'submit/(?P<term>[\d\w-]+)/(?P<committee>[\d\w-]+)/ ', 'vouchers.views.submit_request', ),9 (r'submit/(?P<term>[\d\w-]+)/(?P<committee>[\d\w-]+)/(?P<recipient_type>mit|other)/', 'vouchers.views.submit_request', ), 10 10 url(r'review/(?P<object_id>\d+)/', 'vouchers.views.review_request', name='review_request', ), 11 url(r'generate/', 'vouchers.views.generate_vouchers', name='generate_vouchers', ), 11 url(r'^generate/vouchers.tex$', 'vouchers.views.generate_vouchers', name='generate_vouchers', ), 12 url(r'^generate/rfps.csv$', 'vouchers.views.generate_rfp_specs', name='generate_rfp_specs', ), 12 13 ) -
remit/vouchers/views.py
r4439c52 rdb5be5d 8 8 from django.shortcuts import render_to_response, get_object_or_404 9 9 from django.template import RequestContext 10 from django.http import Http 404, HttpResponseRedirect10 from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseNotAllowed 11 11 import django.forms 12 from django.forms import Form 13 from django.forms import ModelForm 14 from django.forms import ModelChoiceField 12 from django.forms import ChoiceField, Form, ModelForm, ModelChoiceField 15 13 from django.core.urlresolvers import reverse 16 from django.core.mail import send_mail, mail_admins 14 from django.core.mail import send_mail, mail_admins, EmailMessage 17 15 from django.db.models import Q 18 16 from django.template import Context, Template 19 17 from django.template.loader import get_template 20 18 from django.views.decorators.csrf import ensure_csrf_cookie 19 20 import csv 21 import datetime 21 22 import decimal 22 23 23 24 import settings 24 25 26 27 class CommitteesField(ModelChoiceField): 28 def __init__(self, *args, **kargs): 29 base_area = BudgetArea.get_by_path(settings.BASE_COMMITTEE_PATH) 30 self.strip_levels = base_area.depth 31 areas = (base_area.get_descendants() 32 .filter(depth__lte=base_area.depth+settings.COMMITTEE_HIERARCHY_LEVELS) 33 .exclude(name='Holding') 34 ) 35 ModelChoiceField.__init__(self, queryset=areas, 36 help_text='Select the appropriate committe or other budget area', 37 *args, **kargs) 38 39 def label_from_instance(self, obj,): 40 return obj.indented_name(strip_levels=self.strip_levels) 41 42 class SelectRequestBasicsForm(Form): 43 area = CommitteesField() 44 term = ModelChoiceField(queryset = BudgetTerm.objects.all()) 45 recipient_type = ChoiceField(choices=vouchers.models.recipient_type_choices) 46 47 class DocUploadForm(ModelForm): 48 def clean_backing_file(self, ): 49 f = self.cleaned_data['backing_file'] 50 ext = f.name.rsplit('.')[-1] 51 contenttype = f.content_type 52 if ext != 'pdf': 53 raise django.forms.ValidationError(u"Only PDF files are accepted – you submitted a .%s file" % (ext, )) 54 elif contenttype != 'application/pdf': 55 raise django.forms.ValidationError(u"Only PDF files are accepted – you submitted a %s file" % (contenttype, )) 56 else: 57 return f 58 59 class Meta: 60 model = Documentation 61 fields = ( 62 'label', 63 'backing_file', 64 ) 65 66 67 @login_required 68 def select_request_basics(http_request, ): 69 if http_request.method == 'POST': # If the form has been submitted... 70 form = SelectRequestBasicsForm(http_request.POST) # A form bound to the POST data 71 if form.is_valid(): # All validation rules pass 72 term = form.cleaned_data['term'].slug 73 area = form.cleaned_data['area'].id 74 recipient_type = form.cleaned_data['recipient_type'] 75 url = reverse(submit_request, args=[term, area, recipient_type],) 76 return HttpResponseRedirect(url) # Redirect after POST 77 else: 78 form = SelectRequestBasicsForm() # An unbound form 79 80 context = { 81 'form':form, 82 'pagename':'request_reimbursement', 83 } 84 return render_to_response('vouchers/select.html', context, context_instance=RequestContext(http_request), ) 85 86 87 class CommitteeBudgetAreasField(ModelChoiceField): 88 def __init__(self, base_area, *args, **kargs): 89 self.strip_levels = base_area.depth 90 areas = base_area.get_descendants() 91 ModelChoiceField.__init__(self, queryset=areas, 92 help_text='In general, this should be a fully indented budget area, not one with children', 93 *args, **kargs) 94 95 def label_from_instance(self, obj,): 96 return obj.indented_name(strip_levels=self.strip_levels) 97 98 class ExpenseAreasField(ModelChoiceField): 99 def __init__(self, *args, **kargs): 100 base_area = vouchers.models.BudgetArea.get_by_path(['Accounts', 'Expenses']) 101 self.strip_levels = base_area.depth 102 areas = base_area.get_descendants() 103 ModelChoiceField.__init__(self, queryset=areas, 104 help_text='In general, this should be a fully indented budget area, not one with children', 105 *args, **kargs) 106 107 def label_from_instance(self, obj,): 108 return obj.indented_name(strip_levels=self.strip_levels) 109 25 110 class RequestForm(ModelForm): 111 expense_area = ExpenseAreasField() 112 113 def __init__(self, *args, **kwargs): 114 super(RequestForm, self).__init__(*args, **kwargs) 115 if self.instance.recipient_type == 'mit': 116 addr_widget = django.forms.HiddenInput() 117 self.fields['check_to_addr'].widget = addr_widget 118 else: 119 rfp = vouchers.models.RFP 120 error_msgs = dict(invalid=rfp.addr_error) 121 addr_widget = self.fields['check_to_addr'].widget 122 addr_field = django.forms.RegexField(regex=rfp.addr_regex, error_messages=error_msgs) 123 addr_field.widget = addr_widget 124 self.fields['check_to_addr'] = addr_field 125 26 126 class Meta: 27 127 model = ReimbursementRequest … … 39 139 ) 40 140 41 42 class CommitteesField(ModelChoiceField): 43 def __init__(self, *args, **kargs): 44 base_area = BudgetArea.get_by_path(settings.BASE_COMMITTEE_PATH) 45 self.strip_levels = base_area.depth 46 areas = (base_area.get_descendants() 47 .filter(depth__lte=base_area.depth+settings.COMMITTEE_HIERARCHY_LEVELS) 48 .exclude(name='Holding') 49 ) 50 ModelChoiceField.__init__(self, queryset=areas, 51 help_text='Select the appropriate committe or other budget area', 52 *args, **kargs) 53 54 def label_from_instance(self, obj,): 55 return obj.indented_name(strip_levels=self.strip_levels) 56 57 class SelectRequestBasicsForm(Form): 58 area = CommitteesField() 59 term = ModelChoiceField(queryset = BudgetTerm.objects.all()) 60 61 class DocUploadForm(ModelForm): 62 def clean_backing_file(self, ): 63 f = self.cleaned_data['backing_file'] 64 ext = f.name.rsplit('.')[-1] 65 contenttype = f.content_type 66 if ext != 'pdf': 67 raise django.forms.ValidationError(u"Only PDF files are accepted – you submitted a .%s file" % (ext, )) 68 elif contenttype != 'application/pdf': 69 raise django.forms.ValidationError(u"Only PDF files are accepted – you submitted a %s file" % (contenttype, )) 70 else: 71 return f 72 73 class Meta: 74 model = Documentation 75 fields = ( 76 'label', 77 'backing_file', 78 ) 79 80 81 @user_passes_test(lambda u: u.is_authenticated()) 82 def select_request_basics(http_request, ): 83 if http_request.method == 'POST': # If the form has been submitted... 84 form = SelectRequestBasicsForm(http_request.POST) # A form bound to the POST data 85 if form.is_valid(): # All validation rules pass 86 term = form.cleaned_data['term'].slug 87 area = form.cleaned_data['area'].id 88 return HttpResponseRedirect(reverse(submit_request, args=[term, area],)) # Redirect after POST 89 else: 90 form = SelectRequestBasicsForm() # An unbound form 91 92 context = { 93 'form':form, 94 'pagename':'request_reimbursement', 95 } 96 return render_to_response('vouchers/select.html', context, context_instance=RequestContext(http_request), ) 97 98 class CommitteeBudgetAreasField(ModelChoiceField): 99 def __init__(self, base_area, *args, **kargs): 100 self.strip_levels = base_area.depth 101 areas = base_area.get_descendants() 102 ModelChoiceField.__init__(self, queryset=areas, 103 help_text='In general, this should be a fully indented budget area, not one with children', 104 *args, **kargs) 105 106 def label_from_instance(self, obj,): 107 return obj.indented_name(strip_levels=self.strip_levels) 108 109 class ExpenseAreasField(ModelChoiceField): 110 def __init__(self, *args, **kargs): 111 base_area = vouchers.models.BudgetArea.get_by_path(['Accounts', 'Expenses']) 112 self.strip_levels = base_area.depth 113 areas = base_area.get_descendants() 114 ModelChoiceField.__init__(self, queryset=areas, 115 help_text='In general, this should be a fully indented budget area, not one with children', 116 *args, **kargs) 117 118 def label_from_instance(self, obj,): 119 return obj.indented_name(strip_levels=self.strip_levels) 120 121 @user_passes_test(lambda u: u.is_authenticated()) 122 def submit_request(http_request, term, committee): 141 @login_required 142 def submit_request(http_request, term, committee, recipient_type, ): 123 143 term_obj = get_object_or_404(BudgetTerm, slug=term) 124 144 comm_obj = get_object_or_404(BudgetArea, pk=committee) … … 127 147 new_request.submitter = http_request.user.username 128 148 new_request.budget_term = term_obj 149 new_request.recipient_type = recipient_type 129 150 130 151 # Prefill from user information (itself prefilled from LDAP now) … … 137 158 form = RequestForm(http_request.POST, instance=new_request) # A form bound to the POST data 138 159 form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj) 139 form.fields['expense_area'] = ExpenseAreasField()140 160 141 161 if form.is_valid(): # All validation rules pass 142 162 request_obj = form.save() 163 print "request_obj==new_request:", request_obj == new_request 143 164 144 165 # Send email … … 170 191 form = RequestForm(instance=new_request, initial=initial, ) # An unbound form 171 192 form.fields['budget_area'] = CommitteeBudgetAreasField(comm_obj) 172 form.fields['expense_area'] = ExpenseAreasField()173 193 174 194 context = { 175 195 'term':term_obj, 176 196 'comm':comm_obj, 197 'request': new_request, 177 198 'form':form, 178 199 'pagename':'request_reimbursement', … … 185 206 186 207 187 @ user_passes_test(lambda u: u.is_authenticated())208 @login_required 188 209 def review_request(http_request, object_id): 189 210 request_obj = get_object_or_404(ReimbursementRequest, pk=object_id) … … 243 264 show_approve = (http_request.user.has_perm('vouchers.can_approve') 244 265 and request_obj.approval_status == vouchers.models.APPROVAL_STATE_PENDING) 266 post_approve = (http_request.method == 'POST' and 'approve' in http_request.REQUEST) 267 approve_message = '' 268 approve_level = '' 245 269 if show_approve: 246 # Voucherize form 247 # Prefill from certs / config 248 initial = {} 249 initial['name'] = '%s %s' % (http_request.user.first_name, http_request.user.last_name, ) 250 if settings.SIGNATORY_EMAIL: 251 initial['email'] = settings.SIGNATORY_EMAIL 252 else: 253 initial['email'] = http_request.user.email 254 255 approve_message = '' 256 if http_request.method == 'POST' and 'approve' in http_request.REQUEST: 257 approve_form = VoucherizeForm(http_request.POST) 258 if approve_form.is_valid(): 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'], 263 ) 264 approve_message = 'Created new voucher from request' 265 else: 266 approve_form = VoucherizeForm(initial=initial) 270 if not request_obj.documentation: 271 approve_message = "Documentation must be uploaded (above) before approving RFPs." 272 approve_level = 'warn' 273 elif post_approve: 274 request_obj.approve_as_rfp(approver=http_request.user) 275 approve_message = 'Queued RFP from request.' 276 approve_level = 'info' 277 elif post_approve: 278 approve_message = "You attempted to approve a reimbursement request that you could not approve. Most likely, either the voucher has already been approved, or you do not have adequate permissions." 279 approve_level = 'error' 267 280 268 281 context = { … … 271 284 'new': new, 272 285 'doc_form': doc_upload_form, 286 'show_approve': show_approve, 287 'approve_message': approve_message, 288 'approve_level': approve_level, 273 289 } 274 if show_approve:275 context['approve_form'] = approve_form276 context['approve_message'] = approve_message277 290 if show_email: 278 291 context['email_options'] = email_options … … 337 350 return response 338 351 352 # not a view 353 def generate_rfp_specs_download(http_request): 354 # Download unprocessed RFPs 355 rfps = vouchers.models.RFP.objects.all() 356 rfps = rfps.filter(rfp_number=None) 357 response = HttpResponse(mimetype='text/csv') 358 writer = csv.writer(response) 359 cols = ['id', 'name', 360 'payee.mit', 'payee.name', 361 'addr.street', 'addr.city', 'addr.state', 'addr.postal', 362 'item.date', 'item.gl', 'item.co', 'item.amount', 'item.desc', 363 'documentation', 364 ] 365 writer.writerow(cols) 366 for rfp in rfps: 367 item_date = rfp.item_date.strftime("%m/%d/%Y") 368 docs = http_request.build_absolute_uri(rfp.documentation.backing_file.url) 369 writer.writerow((rfp.pk, rfp.name, 370 rfp.payee_type == 'mit', rfp.payee_name, 371 rfp.addr_street, rfp.addr_city, rfp.addr_state, rfp.addr_zip, 372 item_date, rfp.item_gl, rfp.item_co, rfp.item_amount, rfp.item_desc, 373 docs, 374 )) 375 376 # Send mail 377 tmpl = get_template('vouchers/emails/rfps_download.txt') 378 ctx = Context({ 379 'user': http_request.user, 380 'rfps': rfps, 381 }) 382 body = tmpl.render(ctx) 383 mail_admins( 384 'RFPs downloaded: %d by %s' % ( 385 len(rfps), 386 http_request.user, 387 ), 388 body, 389 ) 390 return response 391 392 def send_rfpized_email(rfp): 393 # Should send mail to the submitter, including a coversheet to print and 394 # give to SAO. The attach() method of 395 # https://docs.djangoproject.com/en/dev/topics/email/#emailmessage-objects 396 # may be useful for the coversheet. 397 tmpl = get_template('vouchers/emails/rfp_submitted.txt') 398 ctx = Context({ 399 'rfp': rfp, 400 }) 401 body = tmpl.render(ctx) 402 to = [ rr.check_to_email for rr in rfp.reimbursementrequest_set.all() ] 403 cc = [addr for name, addr in settings.ADMINS] 404 mail = EmailMessage( 405 subject="%sRFP submitted" % (settings.EMAIL_SUBJECT_PREFIX, ), 406 body=body, to=to, cc=cc, 407 ) 408 mail.send() 409 410 # not a view 411 def generate_rfp_specs_results(http_request): 412 reader = csv.DictReader(http_request) 413 rfps = vouchers.models.RFP.objects 414 time = datetime.datetime.now() 415 results = [] 416 dups = 0 417 for line in reader: 418 print line 419 rfp = rfps.get(pk=int(line['id'])) 420 if not rfp.rfp_number: 421 rfp.rfp_number = line['rfp_number'] 422 rfp.rfp_submit_time = time 423 rfp.save() 424 msg = "updated" 425 send_rfpized_email(rfp) 426 else: 427 msg = "additional number: %s" % (line['rfp_number'], ) 428 dups += 1 429 results.append((rfp, msg)) 430 431 # Send mail 432 tmpl = get_template('vouchers/emails/rfps_updated.txt') 433 ctx = Context({ 434 'user': http_request.user, 435 'results': results, 436 'dups': dups, 437 }) 438 body = tmpl.render(ctx) 439 mail_admins( 440 'RFPs created: %d (%d duplicates) by %s' % ( 441 len(results), dups, 442 http_request.user, 443 ), 444 body, 445 ) 446 # For lack of something better to return, just print the email 447 response = HttpResponse(body, mimetype='text/plain') 448 return response 449 450 #@user_passes_test(lambda u: u.has_perm('vouchers.generate_vouchers')) 451 @ensure_csrf_cookie 452 def generate_rfp_specs(http_request): 453 if http_request.method == 'GET': 454 return generate_rfp_specs_download(http_request) 455 elif http_request.method == 'POST': 456 # Upload RFP processing results 457 return generate_rfp_specs_results(http_request) 458 else: 459 content = "Expected GET (download unprocessed RFPs) or POST (upload results)" 460 return HttpResponseNotAllowed(["GET", "POST"], content=content) 461 339 462 def get_related_requests_qobj(user, ): 340 463 return Q(submitter=user.username) | Q(check_to_email=user.email) -
setup.py
rd604f81 r9a12b18 6 6 packages = ["remit"], 7 7 install_requires = [ 8 "pysapweb",8 # Server 9 9 "django", 10 10 "django-treebeard", … … 13 13 # on Ubuntu, try apt-get build-dep python-ldap 14 14 "python-ldap" 15 16 # Client 17 "pysapweb", # SAPWeb 18 "mechanize", # site->client downloader (for SAPWeb or vouchers) 15 19 ], 16 20
Note: See TracChangeset
for help on using the changeset viewer.