Beacon¶
Beacon is a tool for City Purchasers to advertise new opportunities and businesses to sign up to receive information about those opportunities. It is divided into two sections: a public-facing section and an administrative section. The public-facing section contains a list and detail view of both currently available and past opportunities, along with overview informational pages, signup pages, and a page to manage email subscriptions.
The administrative section handles the creation and editing of new opportunities along with an approval flow for opportunities.
Additionally, Beacon has two nightly jobs that run: one nightly job sends emails whenever new opportunities become “published” (see the Opportunity
model for more information), and a job that runs and sends a bi-weekly update with a list of all non-expired opportunities that have been posted in the past two weeks to all signed-up Vendors. See more in the Nightly Jobs section.
See also
How to use Beacon, an internal product guide for more information about the user interface and user experience.
Models Used¶
Forms¶
Forms¶
-
class
MultiCheckboxField
(label=None, validators=None, coerce=<type 'unicode'>, choices=None, **kwargs)[source]¶ Custom multiple select field that displays a list of checkboxes
We have a custom
pre_validate
to handle cases where a user has choices from multiple categories. the validation to pass.Variables: - widget – wtforms ListWidget
- option_widget – wtforms CheckboxInput
-
class
DynamicSelectField
(label=None, validators=None, coerce=<type 'unicode'>, choices=None, **kwargs)[source]¶ Custom dynamic select field
-
class
CategoryForm
(formdata=<object object>, **kwargs)[source]¶ Base form for anything involving Beacon categories
“Categories” and “Subcategories” are originally derived from NIGP codes. NIGP codes can be somewhat hard to parse and aren’t updated incredibly regularly, especially when it comes to things like IT services and software. Therefore, we took time to devise our own descriptions of things that map back to NIGP codes. The category form is the UI representation of that mapping. Each detailed “subcategory” has a parent “category” to which it belongs. Users select the “parent” category they are interested in, and a series of checkboxes are presented to them. This raises some interesting challenges for validation, which are handled in the
process
method.See also
select_multi_checkbox()
- widget used to generate the UI components through jinja templates.
Category
- base object that is used to build the CategoryForm.
Variables: - subcategories – A
MultiCheckboxField
- categories – A
DynamicSelectField
-
pop_categories
(categories=True, subcategories=True)[source]¶ Pop categories and/or subcategories off of the Form’s
data
attributeIn order to prevent wtforms from throwing ValidationErrors improperly, we need to modify some of the internal form data. This method allows us to pop off the categories or subcategories of the form data as necessary.
Parameters: - categories – Pop categories from form’s data if True
- subcategories – Pop subcategories from form’s data if True
Returns: Modified form data with categories and/or subcategories removed as necessary
-
build_categories
(all_categories)[source]¶ Build form’s category and subcategory choices
For our
select_multi_checkbox()
, we need to give both top-level choices for the select field and individual level subcategories. This method creates those and modifies the form in-place to build the appropriate choicesParameters: all_categories – A list of Category
objectsReturns: A dictionary with top-level parent category names as keys and list of that parent’s subcategories as values.
-
display_cleanup
(all_categories=None)[source]¶ Clean up form’s data for display purposes:
- Constructs and modifies the form’s
categories
andsubcategories
- Creates the template-used subcatgories and display categories
- Removed the
Select All
choice from the available categories
Parameters: - all_categories – A list of
Category
objects, - None. If None, defaults to all Categories. (or) –
- Constructs and modifies the form’s
-
process
(formdata=None, obj=None, data=None, **kwargs)[source]¶ Process the form and append data to the
categories
Manually iterates through the flask Request.form, appending valid Categories to the form’s
categories
dataSee also
For more information about parameters, see the Wtforms base form
-
class
VendorSignupForm
(formdata=<object object>, **kwargs)[source]¶ Signup form vendors use to sign up for Beacon updates
The goal here is to lower the barrier to signing up by as much as possible, making it as easy as possible to sign up. This means that very little of this information is required. This form is an implementation of the CategoryForm, which means that it processes categories and subcategories in addition to the below fields.
Variables: - business_name – Name of business, required
- email – Email address of vendor signing up, required, must be unique
- first_name – First name of vendor, optional
- last_name – Last name of vendor, optional
- phone_number – Phone number of vendor, optional
- fax_number – Fax number of vendor, optional
- woman_owned – Whether the business is woman owned, optional
- minority_owned – Whether the business is minority owned, optional
- veteran_owned – Whether the business is veteran owned, optional
- disadvantaged_owned – Whether the business is disadvantaged owned, optional
- subscribed_to_newsletter – Boolean flag for whether a business is signed up to the receive the newsletter
-
class
OpportunitySignupForm
(formdata=<object object>, **kwargs)[source]¶ Signup form vendors can use for individual opportunities
Variables: - business_name – Name of business, required
- email – Email address of vendor signing up, required, must be unique
- also_categories – Flag for whether or not a business should be signed up to receive updates about opportunities with the same categories as this one
-
class
UnsubscribeForm
(formdata=<object object>, **kwargs)[source]¶ Subscription management form, where Vendors can unsubscribe from all different emails
Variables: - email – Email address of vendor signing up, required
- categories – A multicheckbox of all categories the Vendor is signed up to receive emails about
- opportunities – A multicheckbox of all opportunities the Vendor is signed up to receive emails about
- subscribed_to_newsletter – A flag for whether or not the Vendor should receive the biweekly update newsletter
-
class
OpportunityDocumentForm
(formdata=None, obj=None, prefix='', data=None, meta=None, **kwargs)[source]¶ Document subform for the main
OpportunityForm
Variables: - title – Name of document to be uploaded
- document – Actual document file that should be uploaded
-
upload_document
(_id)[source]¶ Take the document and filename and either upload it to S3 or the local uploads folder
Parameters: _id – The id of the Opportunity
the document will be attached toReturns: A two-tuple of (the document name, the document filepath/url)
-
class
OpportunityForm
(formdata=<object object>, **kwargs)[source]¶ Form to create and edit individual opportunities
This form is an implementation of the CategoryForm, which means that it processes categories and subcategories in addition to the below fields.
Variables: - department – link to
Department
that is primarily responsible for administering the RFP, required - opportunity_type – link to
ContractType
objects that have theallow_opportunities
field set to True - contact_email – Email address of the opportunity’s point of contact for questions
- title – Title of the opportunity, required
- description – 500 or less word description of the opportunity, required
- planned_publish – Date when the opportunity should be made public on Beacon
- planned_submission_start – Date when the opportunity opens to accept responses
- planned_submission_end – Date when the opportunity closes and no longer accepts submissions
- vendor_documents_needed – A multicheckbox for all documents that a vendor might need to respond to this opportunity.
- documents – A list of
OpportunityDocumentForm
fields.
See also
ContractType
- The ContractType model informs the construction of the “How to Bid” section in the template
Opportunity
- The base model that powers the form.
-
display_cleanup
(opportunity=None)[source]¶ Cleans up data for display in the form
- Builds the choices for the
vendor_documents_needed
- Formats the contact email for the form
- Localizes the
planned_submission_end
time
See also
Parameters: opportunity – A purchasing.opportunities.model.Opportunity
object or None.- Builds the choices for the
-
data_cleanup
()[source]¶ Cleans up form data for processing and storage
- Pops off categories
- Pops off documents (they are handled separately)
- Sets the foreign keys Opportunity model relationships
- Removes csrf token
Returns: An opportunity_data
dictionary, which can be used to instantiate or modify anOpportunity
instance
- department – link to
Helper functions¶
-
parse_contact
(contact_email, department)[source]¶ Finds or creates a user as the contact
Parameters: - contact_email – The email address of the
User
. If the user cannot be found in the database, the domain of their email must match the configuredCITY_DOMAIN
- department – The
Department
of the user
Returns: The ID of the new/existing contact
- contact_email – The email address of the
-
build_label_tooltip
(name, description)[source]¶ Builds bootstrap-style tooltips for contract documents
Parameters: - name – The name of the document
- description – The description of the document – lives in the tooltip and is shown on hover.
Returns: A formatted label with tooltip
-
select_multi_checkbox
(field, ul_class='', **kwargs)[source]¶ Custom multi-select widget for vendor documents needed
Returns: SelectMulti checkbox widget with tooltip labels
-
class
SignupData
(email, business_name)[source]¶ Small python object to hold default data coming from the Flask session
Parameters: - email – Email address taken from session
- business_name – Business name taken from session
-
init_form
(form, model=None)[source]¶ Initialize a passed form given a model or object
Parameters: - form – The form to initialize
- model – a Model used to instantiate the form with data
If none, use a new instance of
purchasing.opportunities.util.SignupData
Returns: The passed form, initialized with either the passed model or a new instance of
purchasing.opportunities.util.SignupData
-
signup_for_opp
(form, opportunity, multi=False)[source]¶ Sign a vendor up for an opportunity
Generic helper method to handle subscriptions from both the list view (signing up form multiple opportunities) and the detail view (signing up for a single opportunity). Responsible for creation of new Vendor objects if necessary, and sending emails based on the opportunities selected to receive updates about.
Parameters: - form – The relevant subscription form
- opportunity – Either an opportunity model or a list of opportunity ids
- multi – A boolean to flag if there are multiple opportunities that should to subscribe to or a single opportunity
Returns: True if email sent successfully, false otherwise
Views¶
Public-facing¶
-
GET
/beacon/opportunities/expired
¶ View expired contracts
Status Codes: - 200 OK – render the expired opportunities templates
-
POST
/beacon/opportunities
¶ Browse available opportunities
Status Codes: - 200 OK – render the browse template page
- 302 Found – subscribe to one or multiple opportunities via
the
OpportunitySignupForm
-
GET
/beacon/opportunities
¶ Browse available opportunities
Status Codes: - 200 OK – render the browse template page
- 302 Found – subscribe to one or multiple opportunities via
the
OpportunitySignupForm
-
POST
/beacon/manage
¶ Manage a vendor’s signups
Status Codes: - 200 OK – render the
UnsubscribeForm
- 302 Found – post the
UnsubscribeForm
and change the user’s email subscriptions and redirect them back to the management page.
- 200 OK – render the
-
GET
/beacon/manage
¶ Manage a vendor’s signups
Status Codes: - 200 OK – render the
UnsubscribeForm
- 302 Found – post the
UnsubscribeForm
and change the user’s email subscriptions and redirect them back to the management page.
- 200 OK – render the
-
GET
/beacon/
¶ Landing page for opportunities site
Status Codes: - 200 OK – Successfully render the splash page
-
POST
/beacon/opportunities/
(int: opportunity_id)¶ View one opportunity in detail
Status Codes: - 200 OK – Render the opportunity’s detail template
- 302 Found – Signup for this particular opportunity via the
OpportunitySignupForm
-
GET
/beacon/opportunities/
(int: opportunity_id)¶ View one opportunity in detail
Status Codes: - 200 OK – Render the opportunity’s detail template
- 302 Found – Signup for this particular opportunity via the
OpportunitySignupForm
Administration¶
-
GET
/beacon/admin/opportunities/pending
¶ View which contracts are currently pending approval
Status Codes: - 200 OK – Render the pending template
-
POST
/beacon/admin/opportunities/new
¶ Create a new opportunity
Status Codes: - 200 OK – Render the opportunity create/edit template
- 302 Found – Post data for a new opportunity via the
OpportunityForm
and redirect to the edit view of the created opportunity
-
GET
/beacon/admin/opportunities/new
¶ Create a new opportunity
Status Codes: - 200 OK – Render the opportunity create/edit template
- 302 Found – Post data for a new opportunity via the
OpportunityForm
and redirect to the edit view of the created opportunity
-
GET
/beacon/admin/signups
¶ Basic dashboard view for category-level signups
Status Codes: - 200 OK – Download a tab-separated file of all vendor signups
-
GET
/beacon/admin/opportunities/
(int: opportunity_id)/document/
(int: document_id)/remove
¶ Remove a particular opportunity document
See also
OpportunityForm
Status Codes: - 302 Found – Delete the relevant opportunity document and redirect to the edit view for the opportunity whose document was deleted
-
GET
/beacon/admin/opportunities/
(int: opportunity_id)/publish
¶ Publish an opportunity
If an
Opportunity
has been created by a non-admin, it will be stuck in a “pending” state until it has been approved by an admin. This view function handles the publication event for a specificOpportunity
Status Codes: - 200 OK – Publish the relevant opportunity and send the relevant publication emails
- 404 Not Found –
Opportunity
not found
-
GET
/beacon/admin/opportunities/
(int: opportunity_id)/archive
¶ Archives opportunities in pending view
Status Codes: - 302 Found – Archive the
Opportunity
and redirect to the pending view - 404 Not Found –
Opportunity
not found
- 302 Found – Archive the
-
POST
/beacon/admin/opportunities/
(int: opportunity_id)¶ Edit an opportunity
Status Codes: - 200 OK – Render the opportunity create/edit template
- 302 Found – Post data for the relevant opportunity to edit via the
OpportunityForm
and redirect to the edit view of the opportunity
-
GET
/beacon/admin/opportunities/
(int: opportunity_id)¶ Edit an opportunity
Status Codes: - 200 OK – Render the opportunity create/edit template
- 302 Found – Post data for the relevant opportunity to edit via the
OpportunityForm
and redirect to the edit view of the opportunity
Nightly Jobs¶
-
class
BeaconNewOppotunityOpenJob
(time_override=False)[source]¶ Send a nightly email update when new opportunities are posted
-
build_notifications
()[source]¶ Implements EmailJobBase build_notifications method
Returns: list of Notification
objects, one for each new Opportunity. For each Opportunity, theto_email
field is the union of all followers of the opportunity and any followers of any categories that the Opportunity has
-
get_opportunities
()[source]¶ Get new opportunities to send to businesses
Returns: list of Opportunity
objects that are approved (is_public == True
), have not had notification emails sent yet (publish_notification_sent == False
), and are set to publish today (db.func.DATE(Opportunity.planned_publish) == datetime.date.today()
)
-
-
class
BeaconBiweeklyDigestJob
(time_override=False)[source]¶ Send a biweekly update of all non-expired Opportunities posted to Beacon
-
should_run
()[source]¶ Returns true only if we are on the first or fifteenth of the month or time_override is True
-
build_notifications
()[source]¶ Implements EmailJobBase build_notifications method
Returns: list of Notification
objects, one for each non-expired opportunity that has been published since the last Beacon newsletter was sent out
-
get_opportunities
()[source]¶ Get bulk opportunities to send to businesses
Returns: list of Opportunity
objects that have apublished_at
date after the last newsletter was sent out and expire on or after today
-