CURAS / app /fields.py
stevafernandes's picture
Multi-select areas, colour-coded area tags, Creighton students only, titles removed, Creighton blue
4f5fdec verified
Raw History Blame Contribute Delete
9.54 kB
"""Field definitions shared by the public pages, the submission form, the admin
editor and the Excel importer.
Every content field of a listing is described once here so labels, help text,
limits and vocabularies cannot drift apart between the form and the database.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
# Closed vocabularies. The first spelling is canonical; the rest are accepted
# variants (case-insensitive) that are normalised to the canonical spelling.
OPPORTUNITY_TYPES = {
"Credit": ["credit", "academic credit"],
"Volunteer": ["volunteer"],
"Paid": ["paid"],
"Federal Work Study": ["federal work study", "federal workstudy", "federal work-study"],
}
TERMS = {
"Fall": ["fall"],
"Spring": ["spring"],
"Summer": ["summer"],
}
STANDINGS = {
"Freshman": ["freshman", "freshmen"],
"Sophomore": ["sophomore", "sophmore"],
"Junior": ["junior"],
"Senior": ["senior"],
}
# Areas of study that appear in the September 2026 CURAS workbook, with the
# misspellings found there mapped to the canonical spelling.
AREAS_OF_STUDY = {
"All majors": ["all majors", "all majors welcome"],
"American Studies": ["american studies"],
"Art History": ["art history"],
"Biochemistry": ["biochemistry", "biochemisty"],
"Biology": ["biology"],
"Biomedical Physics": ["biomedical physics"],
"Business": ["business"],
"Chemistry": ["chemistry"],
"Classical and Near Eastern Civilizations": ["classical and near eastern civilizations"],
"Classical Languages": ["classical languages"],
"Communication Studies": ["communication studies"],
"Computer Science": ["computer science"],
"Cultural Anthropology": ["cultural anthropology"],
"Data Science": ["data science"],
"Economics": ["economics"],
"Education": ["education"],
"Environmental Science": ["environmental science", "enviromental science", "envirometnal science"],
"Exercise Science": ["exercise science", "exerscise science"],
"Fine and Performing Arts": ["fine and performing arts"],
"Foreign Languages": ["foreign languages"],
"French": ["french"],
"Gender Studies": ["gender studies"],
"German": ["german"],
"Health Administration and Policy": [
"health administration and policy",
"health administration policy",
"health administrationand policy",
],
"History": ["history"],
"International Business": ["international business"],
"International Relations": ["international relations"],
"Mathematics": ["mathematics"],
"Medical Anthropology": ["medical anthropology", "medical anthrolpology"],
"Neuroscience": ["neuroscience", "neursocience"],
"Nursing": ["nursing"],
"Philosophy": ["philosophy"],
"Physics": ["physics"],
"Political Science": ["political science"],
"Pre-Health Professions": ["pre-health professions", "pre health professions"],
"Psychology": ["psychology"],
"Social Work": ["social work"],
"Sociology": ["sociology"],
"Spanish and Hispanic Studies": ["spanish and hispanic studies"],
"Sustainability": ["sustainability"],
"Theology": ["theology"],
}
# Lengths are generous upper bounds to stop abuse, not editorial limits.
SHORT = 300
MEDIUM = 2000
LONG = 8000
@dataclass(frozen=True)
class Field:
key: str
label: str
kind: str # "text" | "textarea" | "url" | "choices"
required: bool = False
help: str = ""
maxlen: int = MEDIUM
choices: Optional[dict] = None # for kind == "choices": canonical -> variants
allow_other: bool = True # for kind == "choices": free-text addition
placeholder: str = ""
# Order here is the order on the submission form, the admin editor and the
# public "full details" view.
CONTENT_FIELDS: list[Field] = [
Field("faculty_name", "Faculty name", "text", required=True, maxlen=SHORT,
help="As it should appear publicly, for example Dr. Jane Smith."),
Field("faculty_profile_url", "Faculty profile link", "url", maxlen=SHORT,
help="Creighton campus directory page or another faculty profile page (https://...)."),
Field("researchworks_url", "ResearchWorks profile link", "url", maxlen=SHORT,
help="Link to your ResearchWorks profile and publications, or a lab website."),
Field("keywords", "Keywords", "text", maxlen=SHORT,
help="Separate keywords with commas."),
Field("description", "Description", "textarea", required=True, maxlen=LONG,
help="What the research is about and what students would do."),
Field("methods", "Methods and techniques", "textarea", maxlen=LONG),
Field("opportunity_type", "Type of opportunity", "choices", required=True,
choices=OPPORTUNITY_TYPES, maxlen=SHORT),
Field("term_availability", "Term availability", "choices", required=True,
choices=TERMS, maxlen=SHORT),
Field("commitment", "Commitment", "textarea", maxlen=MEDIUM,
help="Expected hours per week and duration, for example 5-10 hours per week for two semesters."),
Field("standing", "Standing (year)", "choices", required=True,
choices=STANDINGS, maxlen=SHORT),
Field("prerequisites", "Prerequisites", "textarea", maxlen=MEDIUM,
help="Courses, skills or experience students should have. Write None if there are none."),
Field("areas_of_study", "Areas of study", "choices", required=True,
choices=AREAS_OF_STUDY, maxlen=MEDIUM,
help="Majors or fields the opportunity suits."),
Field("mentorship", "Mentorship", "textarea", maxlen=MEDIUM,
help="How you work with students, for example meeting frequency and supervision style."),
Field("previous_student_work", "Previous student work", "textarea", maxlen=LONG,
help="Examples of past student projects, presentations or publications."),
Field("faculty_note", "Faculty note", "textarea", maxlen=MEDIUM,
help="Anything else students should know."),
Field("application_deadline", "Application deadline", "text", required=True, maxlen=SHORT,
help="Rolling, a date, or a note about current availability.", placeholder="Rolling"),
Field("application_requirements", "Application requirements", "textarea", required=True,
maxlen=MEDIUM, help="What students should send to express interest, for example an introductory email."),
]
FIELD_KEYS = [f.key for f in CONTENT_FIELDS]
FIELDS_BY_KEY = {f.key: f for f in CONTENT_FIELDS}
# Fields shown in the collapsed summary on the public page; the rest are in
# the expandable "Full details" section.
SUMMARY_KEYS = ["opportunity_type", "term_availability", "standing", "areas_of_study", "application_deadline"]
# Submitter fields (not part of the listing itself).
SUBMITTER_FIELDS: list[Field] = [
Field("submitter_name", "Your name", "text", required=True, maxlen=SHORT),
Field("submitter_netid", "Your NetID", "text", required=True, maxlen=64,
help="Your Creighton NetID. Letters and digits only."),
Field("submitter_email", "Your email", "text", maxlen=SHORT,
help="Optional. Used only if CURAS has a question about your submission."),
]
LIST_SEPARATOR = ", "
# Shown on every listing: who may apply.
ELIGIBILITY_NOTE = "Creighton students only"
# Colour code for areas of study, shown as tags on the public page. Each key is
# a CSS class suffix defined in static/style.css (pastel background, dark text).
# Areas that often appear together on one listing are given different colours
# where possible; related fields share a hue.
AREA_COLORS = {
"All majors": "all",
"American Studies": "brown",
"Art History": "pink",
"Biochemistry": "red",
"Biology": "green",
"Biomedical Physics": "sky",
"Business": "gold",
"Chemistry": "teal",
"Classical and Near Eastern Civilizations": "brown",
"Classical Languages": "sky",
"Communication Studies": "orange",
"Computer Science": "indigo",
"Cultural Anthropology": "teal",
"Data Science": "lime",
"Economics": "gold",
"Education": "mint",
"Environmental Science": "lime",
"Exercise Science": "orange",
"Fine and Performing Arts": "magenta",
"Foreign Languages": "purple",
"French": "purple",
"Gender Studies": "magenta",
"German": "purple",
"Health Administration and Policy": "yellow",
"History": "brown",
"International Business": "sky",
"International Relations": "teal",
"Mathematics": "purple",
"Medical Anthropology": "brown",
"Neuroscience": "yellow",
"Nursing": "magenta",
"Philosophy": "magenta",
"Physics": "blue",
"Political Science": "red",
"Pre-Health Professions": "mint",
"Psychology": "pink",
"Social Work": "gold",
"Sociology": "brown",
"Spanish and Hispanic Studies": "purple",
"Sustainability": "mint",
"Theology": "magenta",
}
OTHER_AREA_COLOR = "gray"
def area_color(item: Optional[str]) -> str:
"""CSS colour key for one area-of-study item (free text gets the neutral key)."""
if not item:
return OTHER_AREA_COLOR
wanted = item.strip().lower()
for name, key in AREA_COLORS.items():
if name.lower() == wanted:
return key
return OTHER_AREA_COLOR
def split_list(value: Optional[str]) -> list[str]:
"""Split a stored comma-separated list field into items."""
if not value:
return []
return [p.strip() for p in value.split(",") if p.strip()]