Index: remit/vouchers/admin.py
===================================================================
--- remit/vouchers/admin.py	(revision 7ee21d80d9accb473bcb5ef17b6d3c0058825d3e)
+++ remit/vouchers/admin.py	(revision feed77ca48409cbe8f847764042449441dae1285)
@@ -28,5 +28,6 @@
 
 class RFPAdmin(admin.ModelAdmin):
-    list_display = ('name', 'rfp_number', 'rfp_submit_time', 'payee_type', 'payee_name', 'item_amount', 'item_co', 'item_gl')
+    list_display = ('pk', 'name', 'rfp_number', 'rfp_submit_time', 'payee_type', 'payee_name', 'item_amount', 'item_co', 'item_gl', 'documentation', )
+    list_display_links = ('pk', 'name', )
 
 admin.site.register(vouchers.models.ReimbursementRequest, ReimbursementRequestAdmin)
Index: remit/vouchers/models.py
===================================================================
--- remit/vouchers/models.py	(revision 922e0e5bd2d72c917682d230b4430c4b1dfbe233)
+++ remit/vouchers/models.py	(revision feed77ca48409cbe8f847764042449441dae1285)
@@ -111,4 +111,5 @@
         rfp.item_amount = self.amount
         rfp.item_desc = self.description
+        rfp.documentation = self.documentation
         rfp.save()
         self.create_transfers()
@@ -170,5 +171,5 @@
     processed = models.BooleanField(default=False)
     process_time = models.DateTimeField(blank=True, null=True,)
-    documentation = models.ForeignKey('Documentation', blank=True, null=True, )
+    documentation = models.ForeignKey('Documentation', null=True, )
 
     def mailing_addr_lines(self):
@@ -226,4 +227,6 @@
 
     def fill_addr(self, address):
+        if address == '':
+            return
         match = self.addr_regex.match(address)
         assert match != None
Index: remit/vouchers/urls.py
===================================================================
--- remit/vouchers/urls.py	(revision 2b31097db3be7a94b364aa605e77568622f48fc9)
+++ remit/vouchers/urls.py	(revision feed77ca48409cbe8f847764042449441dae1285)
@@ -9,4 +9,5 @@
     (r'submit/(?P<term>[\d\w-]+)/(?P<committee>[\d\w-]+)/(?P<recipient_type>mit|other)/', 'vouchers.views.submit_request', ),
     url(r'review/(?P<object_id>\d+)/', 'vouchers.views.review_request', name='review_request', ),
-    url(r'generate/', 'vouchers.views.generate_vouchers', name='generate_vouchers', ),
+    url(r'^generate/vouchers.tex$', 'vouchers.views.generate_vouchers', name='generate_vouchers', ),
+    url(r'^generate/rfps.csv$', 'vouchers.views.generate_rfp_specs', name='generate_rfp_specs', ),
 )
Index: remit/vouchers/views.py
===================================================================
--- remit/vouchers/views.py	(revision 638b54784ced1a4b68eeea46e21c058f025bf4e3)
+++ remit/vouchers/views.py	(revision feed77ca48409cbe8f847764042449441dae1285)
@@ -8,13 +8,16 @@
 from django.shortcuts import render_to_response, get_object_or_404
 from django.template import RequestContext
-from django.http import Http404, HttpResponseRedirect
+from django.http import HttpResponse, Http404, HttpResponseRedirect, HttpResponseNotAllowed
 import django.forms
 from django.forms import ChoiceField, Form, ModelForm, ModelChoiceField
 from django.core.urlresolvers import reverse
-from django.core.mail import send_mail, mail_admins
+from django.core.mail import send_mail, mail_admins, EmailMessage
 from django.db.models import Q
 from django.template import Context, Template
 from django.template.loader import get_template
-
+from django.views.decorators.csrf import ensure_csrf_cookie
+
+import csv
+import datetime
 import decimal
 
@@ -347,4 +350,114 @@
     return response
 
+# not a view
+def generate_rfp_specs_download(http_request):
+    # Download unprocessed RFPs
+    rfps = vouchers.models.RFP.objects.all()
+    rfps = rfps.filter(rfp_number=None)
+    response = HttpResponse(mimetype='text/csv')
+    writer = csv.writer(response)
+    cols = ['id', 'name',
+        'payee.mit', 'payee.name',
+        'addr.street', 'addr.city', 'addr.state', 'addr.postal',
+        'item.date', 'item.gl', 'item.co', 'item.amoumnt', 'item.desc',
+        'documentation',
+    ]
+    writer.writerow(cols)
+    for rfp in rfps:
+        item_date = rfp.item_date.strftime("%m/%d/%Y")
+        docs = http_request.build_absolute_uri(rfp.documentation.backing_file.url)
+        writer.writerow((rfp.pk, rfp.name,
+            rfp.payee_type == 'mit', rfp.payee_name,
+            rfp.addr_street, rfp.addr_city, rfp.addr_state, rfp.addr_zip,
+            item_date, rfp.item_gl, rfp.item_co, rfp.item_amount, rfp.item_desc,
+            docs,
+        ))
+
+    # Send mail
+    tmpl = get_template('vouchers/emails/rfps_download.txt')
+    ctx = Context({
+        'user': http_request.user,
+        'rfps': rfps,
+    })
+    body = tmpl.render(ctx)
+    mail_admins(
+        'RFPs downloaded: %d by %s' % (
+            len(rfps),
+            http_request.user,
+        ),
+        body,
+    )
+    return response
+
+def send_rfpized_email(rfp):
+    # Should send mail to the submitter, including a coversheet to print and
+    # give to SAO.  The attach() method of
+    # https://docs.djangoproject.com/en/dev/topics/email/#emailmessage-objects
+    # may be useful for the coversheet.
+    tmpl = get_template('vouchers/emails/rfp_submitted.txt')
+    ctx = Context({
+        'rfp': rfp,
+    })
+    body = tmpl.render(ctx)
+    to = [ rr.check_to_email for rr in rfp.reimbursementrequest_set.all() ]
+    cc = [addr for name, addr in settings.ADMINS]
+    mail = EmailMessage(
+        subject="%sRFP submitted" % (settings.EMAIL_SUBJECT_PREFIX, ),
+        body=body, to=to, cc=cc,
+    )
+    mail.send()
+
+# not a view
+def generate_rfp_specs_results(http_request):
+    reader = csv.DictReader(http_request)
+    rfps = vouchers.models.RFP.objects
+    time = datetime.datetime.now()
+    results = []
+    dups = 0
+    for line in reader:
+        print line
+        rfp = rfps.get(pk=int(line['id']))
+        if not rfp.rfp_number:
+            rfp.rfp_number = line['rfp_number']
+            rfp.rfp_submit_time = time
+            rfp.save()
+            msg = "updated"
+            send_rfpized_email(rfp)
+        else:
+            msg = "additional number: %s" % (line['rfp_number'], )
+            dups += 1
+        results.append((rfp, msg))
+
+    # Send mail
+    tmpl = get_template('vouchers/emails/rfps_updated.txt')
+    ctx = Context({
+        'user': http_request.user,
+        'results': results,
+        'dups': dups,
+    })
+    body = tmpl.render(ctx)
+    mail_admins(
+        'RFPs created: %d (%d duplicates) by %s' % (
+            len(results), dups,
+            http_request.user,
+        ),
+        body,
+    )
+    # For lack of something better to return, just print the email
+    response = HttpResponse(body)
+    return response
+
+#@user_passes_test(lambda u: u.has_perm('vouchers.generate_vouchers'))
+@ensure_csrf_cookie
+def generate_rfp_specs(http_request):
+    if http_request.method == 'GET':
+        return generate_rfp_specs_download(http_request)
+    elif http_request.method == 'POST':
+        # Upload RFP processing results
+        return generate_rfp_specs_results(http_request)
+    else:
+        content = "Expected GET (download unprocessed RFPs) or POST (upload results)"
+        return HttpResponseNotAllowed(["GET", "POST"], content=content)
+
 def get_related_requests_qobj(user, ):
     return Q(submitter=user.username) | Q(check_to_email=user.email)
