<gh_stars>1-10
# -*- coding: utf-8 -*-

"""Data models."""

from django.conf import settings
from django.contrib.auth.models import User
from django.core.validators import FileExtensionValidator
from django.db import models
from django.urls import reverse
from django.utils.safestring import mark_safe
from djbeca.core import choices
from djimix.people.departments import chair_departments
from djimix.people.utils import get_position
from djtools.fields.helpers import upload_to_path
from djtools.utils.users import in_group
from taggit.managers import TaggableManager


ALLOWED_EXTENSIONS = ['xls', 'xlsx', 'pdf']
FILE_VALIDATORS = [
    FileExtensionValidator(allowed_extensions=ALLOWED_EXTENSIONS),
]


def limit_subcontracts():
    """Return choices for m2m field in ProposalImpact data model."""
    ids = [
        choice.id for choice in GenericChoice.objects.filter(
            tags__name__in=['Subcontracts'],
        ).order_by('rank')
    ]
    return {'id__in': ids}


class GenericChoice(models.Model):
    """Choices for model and form fields that accept for multiple values."""

    name = models.CharField(max_length=255)
    value = models.CharField(unique=True, max_length=255)
    rank = models.IntegerField(
        verbose_name="Ranking",
        null=True,
        blank=True,
        default=0,
        help_text="A number that determines this object's position in a list.",
    )
    active = models.BooleanField(
        help_text="""
            Do you want the field to be visable on the public submission form?
        """,
        verbose_name="Is active?",
        default=True,
    )
    admin = models.BooleanField(
        verbose_name="Administrative only", default=False,
    )
    tags = TaggableManager(blank=True)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['rank']

    def __str__(self):
        """Default data for display."""
        return self.name


class Proposal(models.Model):
    """Proposal to pursue funding."""

    # meta
    created_at = models.DateTimeField('Date Created', auto_now_add=True)
    updated_at = models.DateTimeField('Date Updated', auto_now=True)
    user = models.ForeignKey(
        User,
        editable=settings.DEBUG,
        on_delete=models.CASCADE,
    )
    # Division Dean or Department VP approval
    level3 = models.BooleanField(default=False)
    # anyone in the workflow decline the proposal at this point
    decline = models.BooleanField(default=False)
    # set to True when steps 1 & 2 have been approved by all and
    # post_save signal sends email to OSP
    email_approved = models.BooleanField(default=False)
    # PI has submitted proposal for final approval
    save_submit = models.BooleanField(default=False)
    # OSP will close a proposal when they have determined it is
    # over so that the PI can resubmit if they so choose
    closed = models.BooleanField(default=False)
    # signifies that it has been reopened
    opened = models.BooleanField(default=False)
    # signifies that it has been awarded funds
    awarded = models.BooleanField(default=False)

    # Basic Proposal Elements
    proposal_type = models.CharField(
        "What type of proposal submission is this?",
        max_length=128,
        choices=choices.PROPOSAL_TYPE_CHOICES,
        help_text=mark_safe(
            """
            <ul class='block'>
            <li>
              <b>New</b>: never submitted this proposal to this agency before
            </li>
            <li>
              <b>Revised</b>: per Funder Request: update of submitted proposal,
              because funder requested changes
            </li>
            <li>
              <b>Re-Submission</b>: submitted proposal in prior round of
              funding, re-submitting proposal for new round
            </li>
            </ul>
        """,
        ),
    )
    proposal_type_other = models.CharField(
        "If 'Other', please provide details",
        max_length=128,
        null=True,
        blank=True,
    )
    funding_agency_program_name = models.CharField(
        "Funding/Sponsor Agency Name and Grant Program Name",
        max_length=128,
    )
    grant_agency_funding_source = models.CharField(
        "What type of funding source is the granting agency?",
        max_length=128,
        choices=choices.FUNDING_SOURCE_CHOICES,
    )
    grant_agency_funding_source_other = models.CharField(
        "If 'Other', please provide details",
        max_length=128,
        null=True,
        blank=True,
    )
    grant_agency_url = models.CharField("Solicitation Website", max_length=512)
    grant_deadline_date = models.DateField("Proposal Deadline Date")
    # Investigator Information
    department = models.CharField(max_length=12)
    """
    NOTE "Co-Principal Investigators & Associated Institution"
    are ProposalContact() Foreign Key relationships.
    Name, Instituion fields [limit 5]
    """
    lead_institution = models.CharField(
        "In this proposal, Carthage is considered:",
        max_length=24,
        choices=choices.LEAD_INSTITUTION_CHOICES,
        null=True,
        blank=True,
    )
    lead_institution_contact = models.TextField(
        "Lead institution contact information",
        help_text="Sponsored Programs Office (or equivalent)",
        null=True,
        blank=True,
    )
    # Project Overview
    title = models.CharField("Project title", max_length=255)
    start_date = models.DateField("Project start date")
    end_date = models.DateField("Project end date")
    project_type = models.CharField(
        max_length=128,
        choices=choices.PROJECT_TYPE_CHOICES,
    )
    project_type_other = models.CharField(
        "If 'Other', please provide details",
        max_length=128,
        null=True,
        blank=True,
    )
    summary = models.TextField(
        "Program summary (~500 words)",
        help_text="""
            Provide a brief description of your proposed project.
            How does your project help further Carthage's mission?
            Describe any partnerships or collaborative relationships
            needed to execute the project. 
        """,
    )
    # Project Funding / Budget Overview
    budget_total = models.DecimalField(
        "Total Program Cost",
        decimal_places=2,
        max_digits=16,
        help_text="List the total amount budgeted for this project",
    )
    budget_summary = models.TextField(
        "Budget Summary (~500 words)",
        help_text=mark_safe(
        """
            Describe your funding plan: 
            <ul>
                <li>
                    What types of expenses do you expect to have
                    (summer salary, travel, student research dollars,
                    admin support, materials and supplies, equipment,
                    etc...)?
                </li>
                <li>
                    Will this grant provide new funds for a new project
                    or new funds for an existing project? Will the grant
                    provide any budget relief for Carthage? Describe.
                </li>
                <li>
                    Will other gift or grant dollars be used to support
                    this project? Describe.
                </li>
                    What, if any financial commitments
                    (match/ cost-share/ in-kind will Carthage need to
                    make if awarded these funds?
                </li>
                <li>Does the grant allow for indirect costs? </li>
            </ul>
        """),
    )
    # additional comments
    comments = models.TextField(
        null=True,
        blank=True,
        help_text="Provide any additional comments if need be",
    )
    # administrative comments
    admin_comments = models.TextField(
        null=True,
        blank=True,
        help_text="""
            Provide any administrative comments that you might want
            others to consider.
        """,
    )

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal'

    def __unicode__(self):
        """Default data for display."""
        return "{0} ({1})".format(self.title, self.id)

    def get_absolute_url(self):
        """Returns the FQDN URL."""
        return 'https://{0}{1}'.format(
            settings.SERVER_URL,
            reverse('proposal_detail', args=(self.id,)),
        )

    def get_update_url(self):
        """Returns the URL for updating the proposal."""
        return 'https://{0}{1}'.format(
            settings.SERVER_URL,
            reverse('proposal_update', args=(self.id,)),
        )

    def get_slug(self):
        """Returns the proposal slug."""
        return 'proposal/'

    def permissions(self, user):
        """What can the user access in terms of viewing & approval process."""
        osp_group = settings.OSP_GROUP
        veep = get_position(settings.VEEP_TPOS)
        provost = get_position(settings.PROV_TPOS)

        perms = {
            'view': False,
            'approve': False,
            'decline': False,
            'close': False,
            'open': False,
            'needswork': False,
            'superuser': False,
            'approver': False,
            'level3': False,
            'level2': False,
            'level1': False,
        }

        # in_group includes an exception for superusers
        group = in_group(user, osp_group)

        chair_depts = chair_departments(user.id)

        # dean or chair?
        dc = chair_depts[1]

        # Dean?
        if dc == 'dean':
            perms['view'] = True
            perms['level3'] = True
            perms['open'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'level3'
        # VP for Business?
        elif user.id == veep.id:
            perms['view'] = True
            perms['level2'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'level2'
        # Provost?
        elif user.id == provost.id:
            perms['view'] = True
            perms['level1'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'level1'
            # provost might be an adhoc approver
            for approver in self.approvers.all():
                if approver.user == user:
                    perms['approver'] = True
        # Superuser?
        elif group:
            perms['view'] = True
            perms['open'] = True
            perms['close'] = True
            perms['superuser'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'superuser'
        elif self.user == user:
            perms['view'] = True
            perms['open'] = True
        # Ad-hoc approver?
        else:
            for approver in self.approvers.all():
                if approver.user == user:
                    perms['view'] = True
                    perms['approver'] = True
                    perms['needswork'] = True
                    perms['decline'] = True
                    perms['approve'] = 'approver'
                    break

        return perms

    # at the moment, we assume all approvers will be responsible for
    # step 1 AND step 2. in the future, i suspect that this might change.
    def step1(self):
        """Check if step 1 has been approved."""
        # Dean or Department VP
        approved = self.level3
        for approver in self.approvers.all():
            if not approver.step1:
                approved = False
                break
        return approved

    def step2(self):
        """Check if step 2 has been approved."""
        try:
            approved = (
                self.impact.level1 and
                self.impact.level2 and
                self.impact.level3
            )
        except AttributeError:
            approved = False

        if approved:
            for approver in self.approvers.all():
                if not approver.step2:
                    approved = False
                    break
        return approved

    def ready_level1(self):
        """Check if VP for Business (level2) & Provost (level1) can approve."""
        approved = False
        try:
            if self.impact.level3:
                approved = True
        except AttributeError:
            approved = False

        if approved:
            for approver in self.approvers.all():
                if not approver.step2:
                    approved = False
                    break

        return approved


class ProposalImpact(models.Model):
    """Proposal impact data."""

    # meta
    created_at = models.DateTimeField("Date Created", auto_now_add=True)
    updated_at = models.DateTimeField("Date Updated", auto_now=True)
    proposal = models.OneToOneField(
        Proposal,
        related_name='impact',
        on_delete=models.CASCADE,
    )
    # status
    level3 = models.BooleanField(default=False)  # Division Dean
    level2 = models.BooleanField(default=False)  # VP for Business
    level1 = models.BooleanField(default=False)  # Provost

    # institutional impacts
    institutional_funds = models.CharField(
        "Will institutional or departmental funds be used in this proposal?",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    institutional_funds_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    indirect_funds_solicitation = models.CharField(
        "Does the sponsor allow the inclusion of indirect in the budget?",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    indirect_funds_solicitation_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    subcontracts = models.ManyToManyField(
        GenericChoice,
        verbose_name="Does your proposal include any of the following?",
        limit_choices_to=limit_subcontracts,
        blank=True,
        related_name="subcontracts",
        help_text="Check all that apply",
    )
    subcontracts_details = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="""
            If you checked any of the boxes above, please provide additional
            details.
        """,
    )
    course_relief = models.CharField(
        """
            Will this project require that your department hire someone
            to teach the courses you are scheduled to teach
            or any other type of course relief?
        """,
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    course_relief_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    new_hires = models.CharField(
        "Will this project create a new position at Carthage?",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    new_hires_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    infrastructure_requirements = models.CharField(
        "Is new or renovated space required?",
        max_length=8,
        choices=choices.UNSURE_CHOICES,
    )
    infrastructure_requirements_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    # begin questions after instructional text
    international = models.CharField(
        "International or off-campus studies",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    international_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    event_services = models.CharField(
        "Conferences and event services",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    event_services_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    financial_aid = models.CharField(
        "Financial aid / scholarships",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    financial_aid_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    students_involved = models.CharField(
        "Student Employment or Work Study",
        max_length=16,
        choices=choices.BINARY_CHOICES,
    )
    students_involved_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    students_stipends = models.CharField(
        "Student Stipends/ Scholarships/ Fellowships",
        max_length=16,
        choices=choices.BINARY_CHOICES,
    )
    students_stipends_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    personnel_salary = models.CharField(
        "Job posting, hiring, salary/wage changes",
        max_length=16,
        choices=choices.BINARY_CHOICES,
    )
    personnel_salary_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    data_management = models.CharField(
        "Institutional Data",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    data_management_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    tech_support = models.CharField(
        "Computer support, computer equipment, data management needs",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    tech_support_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    marketing = models.CharField(
        "Brochures, PR, websites",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    marketing_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    contract_procurement = models.CharField(
        "Contract Review and Negotiation",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    contract_procurement_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    purchase_equipment = models.CharField(
        "Equipment Purchases (over $5000)",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    purchase_equipment_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    subaward_monitoring = models.CharField(
        "Sub Award Monitoring",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    subaward_monitoring_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    human_subjects = models.CharField(
        "IRB (Human Subjects Research)",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    human_subjects_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Detail any IRB approval and submission date",
    )
    animal_subjects = models.CharField(
        "IACUC (Animal Research)",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    animal_subjects_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Detail any IACUC approval and submission dates",
    )
    admin_comments = models.TextField(
        null=True,
        blank=True,
        help_text="""
            Provide any administrative comments that you might want
            others to consider.
        """,
    )
    disclosure_assurance = models.BooleanField(default=False)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_impact'

    def title(self):
        """Obtain the proposal title from foreign key Proposal."""
        return self.proposal.title

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-impact/'


class ProposalBudget(models.Model):
    """Proposal budget data."""

    # meta
    created_at = models.DateTimeField(
        'Date Created', auto_now_add=True,
    )
    updated_at = models.DateTimeField(
        'Date Updated', auto_now=True,
    )
    proposal = models.OneToOneField(
        Proposal,
        editable=False,
        related_name='budget',
        on_delete=models.CASCADE,
    )
    # Costs and totals
    total = models.DecimalField(
        "Total Program Cost",
        decimal_places=2,
        max_digits=16,
        help_text="Provide the total amount budgeted for this project",
        null=True,
        blank=True,
    )
    total_funding = models.DecimalField(
        "Provide the total funds requested from this grantor",
        decimal_places=2,
        max_digits=16,
        help_text="Provide the total amount of the funding request",
        null=True,
        blank=True,
    )
    plan_b = models.TextField(
        "Briefly describe your plan for this project if not awarded this grant",
        help_text="~200 words",
    )
    # Files
    budget_final = models.FileField(
        "Final Budget for Review",
        upload_to=upload_to_path,
        validators=FILE_VALIDATORS,
        max_length=512,
        help_text="PDF or Excel Format Only",
    )
    budget_justification_final = models.FileField(
        "Final Budget Justification for Review",
        upload_to=upload_to_path,
        validators=FILE_VALIDATORS,
        max_length=512,
        help_text="PDF or Excel Format Only",
        null=True,
        blank=True,
    )

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_budget'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-budget/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}".format(self.proposal.title)


class ProposalBudgetFunding(models.Model):
    """Proposal budget funding data."""

    # meta
    created_at = models.DateTimeField(
        'Date Created', auto_now_add=True,
    )
    updated_at = models.DateTimeField(
        'Date Updated', auto_now=True,
    )
    budget = models.ForeignKey(
        ProposalBudget,
        editable=False,
        related_name='funding',
        on_delete=models.CASCADE,
    )
    amount = models.DecimalField(
        "Amount/Currency",
        decimal_places=2,
        max_digits=16,
        null=True,
        blank=True,
    )
    source = models.CharField(
        "Describe Other Sources of Funding for the Project",
        max_length=24,
        choices=choices.BUDGET_FUNDING_SOURCE,
    )
    status = models.CharField(
        "Status of these Funds",
        max_length=24,
        choices=choices.BUDGET_FUNDING_STATUS,
    )

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_budget_funding'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-budget-funding/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}".format(self.budget.proposal.title)


class ProposalDocument(models.Model):
    """Proposal supporting documents."""

    # meta
    created_at = models.DateTimeField(
        "Date Created",
        auto_now_add=True,
    )
    proposal = models.ForeignKey(
        Proposal,
        editable=False,
        related_name='documents',
        on_delete=models.CASCADE,
    )
    name = models.CharField(
        "Name or short description of the file",
        max_length=128,
        null=True,
        blank=True,
    )
    phile = models.FileField(
        "Supporting Document",
        upload_to=upload_to_path,
        validators=FILE_VALIDATORS,
        max_length=512,
        help_text="PDF or Excel Format Only",
        null=True,
        blank=True,
    )
    tags = TaggableManager(blank=True)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_document'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-document/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}: {1}".format(self.name, self.proposal.title)


class ProposalContact(models.Model):
    """Proposal contact data."""

    created_at = models.DateTimeField(
        "Date Created",
        auto_now_add=True,
    )
    proposal = models.ForeignKey(
        Proposal,
        editable=False,
        related_name='contact',
        on_delete=models.CASCADE,
    )
    name = models.CharField(
        max_length=128,
        null=True,
        blank=True,
    )
    institution = models.CharField(
        max_length=128,
        null=True,
        blank=True,
    )
    email = models.EmailField(
        "Your email address",
        max_length=128,
        null=True,
        blank=True,
    )
    tags = TaggableManager(blank=True)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_contact'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-contact/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}: {1}".format(self.name, self.institution)


class ProposalApprover(models.Model):
    """Additional folks who need to approve a proposal."""

    user = models.ForeignKey(
        User,
        related_name='approver_user',
        on_delete=models.CASCADE,
    )
    proposal = models.ForeignKey(
        Proposal,
        related_name='approvers',
        on_delete=models.CASCADE,
    )
    # this field is not in use at the moment but i suspect
    # OSP will want to reactivate it in the future
    steps = models.CharField(
        max_length=4,
        default='3',
        choices=choices.PROPOSAL_STEPS_CHOICES,
    )
    replace = models.CharField(
        max_length=24,
        default='level3',
        choices=choices.APPROVAL_LEVEL_CHOICES,
        null=True,
        blank=True,
    )
    step1 = models.BooleanField(default=True)
    step2 = models.BooleanField(default=True)

    class Meta:
        """Attributes about the data model and admin options."""

        db_table = 'core_proposal_approver'

    def first_name(self):
        """Return the approver's first name."""
        return self.user.first_name

    def last_name(self):
        """Return the approver's last name."""
        return self.user.last_name

    def email(self):
        """Return the approver's email."""
        return self.user.email

    def title(self):
        """Return the proposal title."""
        return self.proposal.title

# -*- coding: utf-8 -*-

"""Data models."""

from django.conf import settings
from django.contrib.auth.models import User
from django.core.validators import FileExtensionValidator
from django.db import models
from django.urls import reverse
from django.utils.safestring import mark_safe
from djbeca.core import choices
from djimix.people.departments import chair_departments
from djimix.people.utils import get_position
from djtools.fields.helpers import upload_to_path
from djtools.utils.users import in_group
from taggit.managers import TaggableManager


ALLOWED_EXTENSIONS = ['xls', 'xlsx', 'pdf']
FILE_VALIDATORS = [
    FileExtensionValidator(allowed_extensions=ALLOWED_EXTENSIONS),
]


def limit_subcontracts():
    """Return choices for m2m field in ProposalImpact data model."""
    ids = [
        choice.id for choice in GenericChoice.objects.filter(
            tags__name__in=['Subcontracts'],
        ).order_by('rank')
    ]
    return {'id__in': ids}


class GenericChoice(models.Model):
    """Choices for model and form fields that accept for multiple values."""

    name = models.CharField(max_length=255)
    value = models.CharField(unique=True, max_length=255)
    rank = models.IntegerField(
        verbose_name="Ranking",
        null=True,
        blank=True,
        default=0,
        help_text="A number that determines this object's position in a list.",
    )
    active = models.BooleanField(
        help_text="""
            Do you want the field to be visable on the public submission form?
        """,
        verbose_name="Is active?",
        default=True,
    )
    admin = models.BooleanField(
        verbose_name="Administrative only", default=False,
    )
    tags = TaggableManager(blank=True)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['rank']

    def __str__(self):
        """Default data for display."""
        return self.name


class Proposal(models.Model):
    """Proposal to pursue funding."""

    # meta
    created_at = models.DateTimeField('Date Created', auto_now_add=True)
    updated_at = models.DateTimeField('Date Updated', auto_now=True)
    user = models.ForeignKey(
        User,
        editable=settings.DEBUG,
        on_delete=models.CASCADE,
    )
    # Division Dean or Department VP approval
    level3 = models.BooleanField(default=False)
    # anyone in the workflow decline the proposal at this point
    decline = models.BooleanField(default=False)
    # set to True when steps 1 & 2 have been approved by all and
    # post_save signal sends email to OSP
    email_approved = models.BooleanField(default=False)
    # PI has submitted proposal for final approval
    save_submit = models.BooleanField(default=False)
    # OSP will close a proposal when they have determined it is
    # over so that the PI can resubmit if they so choose
    closed = models.BooleanField(default=False)
    # signifies that it has been reopened
    opened = models.BooleanField(default=False)
    # signifies that it has been awarded funds
    awarded = models.BooleanField(default=False)

    # Basic Proposal Elements
    proposal_type = models.CharField(
        "What type of proposal submission is this?",
        max_length=128,
        choices=choices.PROPOSAL_TYPE_CHOICES,
        help_text=mark_safe(
            """
            <ul class='block'>
            <li>
              <b>New</b>: never submitted this proposal to this agency before
            </li>
            <li>
              <b>Revised</b>: per Funder Request: update of submitted proposal,
              because funder requested changes
            </li>
            <li>
              <b>Re-Submission</b>: submitted proposal in prior round of
              funding, re-submitting proposal for new round
            </li>
            </ul>
        """,
        ),
    )
    proposal_type_other = models.CharField(
        "If 'Other', please provide details",
        max_length=128,
        null=True,
        blank=True,
    )
    funding_agency_program_name = models.CharField(
        "Funding/Sponsor Agency Name and Grant Program Name",
        max_length=128,
    )
    grant_agency_funding_source = models.CharField(
        "What type of funding source is the granting agency?",
        max_length=128,
        choices=choices.FUNDING_SOURCE_CHOICES,
    )
    grant_agency_funding_source_other = models.CharField(
        "If 'Other', please provide details",
        max_length=128,
        null=True,
        blank=True,
    )
    grant_agency_url = models.CharField("Solicitation Website", max_length=512)
    grant_deadline_date = models.DateField("Proposal Deadline Date")
    # Investigator Information
    department = models.CharField(max_length=12)
    """
    NOTE "Co-Principal Investigators & Associated Institution"
    are ProposalContact() Foreign Key relationships.
    Name, Instituion fields [limit 5]
    """
    lead_institution = models.CharField(
        "In this proposal, Carthage is considered:",
        max_length=24,
        choices=choices.LEAD_INSTITUTION_CHOICES,
        null=True,
        blank=True,
    )
    lead_institution_contact = models.TextField(
        "Lead institution contact information",
        help_text="Sponsored Programs Office (or equivalent)",
        null=True,
        blank=True,
    )
    # Project Overview
    title = models.CharField("Project title", max_length=255)
    start_date = models.DateField("Project start date")
    end_date = models.DateField("Project end date")
    project_type = models.CharField(
        max_length=128,
        choices=choices.PROJECT_TYPE_CHOICES,
    )
    project_type_other = models.CharField(
        "If 'Other', please provide details",
        max_length=128,
        null=True,
        blank=True,
    )
    summary = models.TextField(
        "Program summary (~500 words)",
        help_text="""
            Provide a brief description of your proposed project.
            How does your project help further Carthage's mission?
            Describe any partnerships or collaborative relationships
            needed to execute the project. 
        """,
    )
    # Project Funding / Budget Overview
    budget_total = models.DecimalField(
        "Total Program Cost",
        decimal_places=2,
        max_digits=16,
        help_text="List the total amount budgeted for this project",
    )
    budget_summary = models.TextField(
        "Budget Summary (~500 words)",
        help_text=mark_safe(
        """
            Describe your funding plan: 
            <ul>
                <li>
                    What types of expenses do you expect to have
                    (summer salary, travel, student research dollars,
                    admin support, materials and supplies, equipment,
                    etc...)?
                </li>
                <li>
                    Will this grant provide new funds for a new project
                    or new funds for an existing project? Will the grant
                    provide any budget relief for Carthage? Describe.
                </li>
                <li>
                    Will other gift or grant dollars be used to support
                    this project? Describe.
                </li>
                    What, if any financial commitments
                    (match/ cost-share/ in-kind will Carthage need to
                    make if awarded these funds?
                </li>
                <li>Does the grant allow for indirect costs? </li>
            </ul>
        """),
    )
    # additional comments
    comments = models.TextField(
        null=True,
        blank=True,
        help_text="Provide any additional comments if need be",
    )
    # administrative comments
    admin_comments = models.TextField(
        null=True,
        blank=True,
        help_text="""
            Provide any administrative comments that you might want
            others to consider.
        """,
    )

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal'

    def __unicode__(self):
        """Default data for display."""
        return "{0} ({1})".format(self.title, self.id)

    def get_absolute_url(self):
        """Returns the FQDN URL."""
        return 'https://{0}{1}'.format(
            settings.SERVER_URL,
            reverse('proposal_detail', args=(self.id,)),
        )

    def get_update_url(self):
        """Returns the URL for updating the proposal."""
        return 'https://{0}{1}'.format(
            settings.SERVER_URL,
            reverse('proposal_update', args=(self.id,)),
        )

    def get_slug(self):
        """Returns the proposal slug."""
        return 'proposal/'

    def permissions(self, user):
        """What can the user access in terms of viewing & approval process."""
        osp_group = settings.OSP_GROUP
        veep = get_position(settings.VEEP_TPOS)
        provost = get_position(settings.PROV_TPOS)

        perms = {
            'view': False,
            'approve': False,
            'decline': False,
            'close': False,
            'open': False,
            'needswork': False,
            'superuser': False,
            'approver': False,
            'level3': False,
            'level2': False,
            'level1': False,
        }

        # in_group includes an exception for superusers
        group = in_group(user, osp_group)

        chair_depts = chair_departments(user.id)

        # dean or chair?
        dc = chair_depts[1]

        # Dean?
        if dc == 'dean':
            perms['view'] = True
            perms['level3'] = True
            perms['open'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'level3'
        # VP for Business?
        elif user.id == veep.id:
            perms['view'] = True
            perms['level2'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'level2'
        # Provost?
        elif user.id == provost.id:
            perms['view'] = True
            perms['level1'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'level1'
            # provost might be an adhoc approver
            for approver in self.approvers.all():
                if approver.user == user:
                    perms['approver'] = True
        # Superuser?
        elif group:
            perms['view'] = True
            perms['open'] = True
            perms['close'] = True
            perms['superuser'] = True
            perms['needswork'] = True
            perms['decline'] = True
            perms['approve'] = 'superuser'
        elif self.user == user:
            perms['view'] = True
            perms['open'] = True
        # Ad-hoc approver?
        else:
            for approver in self.approvers.all():
                if approver.user == user:
                    perms['view'] = True
                    perms['approver'] = True
                    perms['needswork'] = True
                    perms['decline'] = True
                    perms['approve'] = 'approver'
                    break

        return perms

    # at the moment, we assume all approvers will be responsible for
    # step 1 AND step 2. in the future, i suspect that this might change.
    def step1(self):
        """Check if step 1 has been approved."""
        # Dean or Department VP
        approved = self.level3
        for approver in self.approvers.all():
            if not approver.step1:
                approved = False
                break
        return approved

    def step2(self):
        """Check if step 2 has been approved."""
        try:
            approved = (
                self.impact.level1 and
                self.impact.level2 and
                self.impact.level3
            )
        except AttributeError:
            approved = False

        if approved:
            for approver in self.approvers.all():
                if not approver.step2:
                    approved = False
                    break
        return approved

    def ready_level1(self):
        """Check if VP for Business (level2) & Provost (level1) can approve."""
        approved = False
        try:
            if self.impact.level3:
                approved = True
        except AttributeError:
            approved = False

        if approved:
            for approver in self.approvers.all():
                if not approver.step2:
                    approved = False
                    break

        return approved


class ProposalImpact(models.Model):
    """Proposal impact data."""

    # meta
    created_at = models.DateTimeField("Date Created", auto_now_add=True)
    updated_at = models.DateTimeField("Date Updated", auto_now=True)
    proposal = models.OneToOneField(
        Proposal,
        related_name='impact',
        on_delete=models.CASCADE,
    )
    # status
    level3 = models.BooleanField(default=False)  # Division Dean
    level2 = models.BooleanField(default=False)  # VP for Business
    level1 = models.BooleanField(default=False)  # Provost

    # institutional impacts
    institutional_funds = models.CharField(
        "Will institutional or departmental funds be used in this proposal?",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    institutional_funds_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    indirect_funds_solicitation = models.CharField(
        "Does the sponsor allow the inclusion of indirect in the budget?",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    indirect_funds_solicitation_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    subcontracts = models.ManyToManyField(
        GenericChoice,
        verbose_name="Does your proposal include any of the following?",
        limit_choices_to=limit_subcontracts,
        blank=True,
        related_name="subcontracts",
        help_text="Check all that apply",
    )
    subcontracts_details = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="""
            If you checked any of the boxes above, please provide additional
            details.
        """,
    )
    course_relief = models.CharField(
        """
            Will this project require that your department hire someone
            to teach the courses you are scheduled to teach
            or any other type of course relief?
        """,
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    course_relief_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    new_hires = models.CharField(
        "Will this project create a new position at Carthage?",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    new_hires_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    infrastructure_requirements = models.CharField(
        "Is new or renovated space required?",
        max_length=8,
        choices=choices.UNSURE_CHOICES,
    )
    infrastructure_requirements_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    # begin questions after instructional text
    international = models.CharField(
        "International or off-campus studies",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    international_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    event_services = models.CharField(
        "Conferences and event services",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    event_services_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    financial_aid = models.CharField(
        "Financial aid / scholarships",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    financial_aid_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    students_involved = models.CharField(
        "Student Employment or Work Study",
        max_length=16,
        choices=choices.BINARY_CHOICES,
    )
    students_involved_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    students_stipends = models.CharField(
        "Student Stipends/ Scholarships/ Fellowships",
        max_length=16,
        choices=choices.BINARY_CHOICES,
    )
    students_stipends_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    personnel_salary = models.CharField(
        "Job posting, hiring, salary/wage changes",
        max_length=16,
        choices=choices.BINARY_CHOICES,
    )
    personnel_salary_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    data_management = models.CharField(
        "Institutional Data",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    data_management_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    tech_support = models.CharField(
        "Computer support, computer equipment, data management needs",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    tech_support_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    marketing = models.CharField(
        "Brochures, PR, websites",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    marketing_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    contract_procurement = models.CharField(
        "Contract Review and Negotiation",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    contract_procurement_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    purchase_equipment = models.CharField(
        "Equipment Purchases (over $5000)",
        max_length=8,
        choices=choices.BINARY_CHOICES,
    )
    purchase_equipment_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    subaward_monitoring = models.CharField(
        "Sub Award Monitoring",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    subaward_monitoring_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Please provide additional details",
    )
    human_subjects = models.CharField(
        "IRB (Human Subjects Research)",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    human_subjects_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Detail any IRB approval and submission date",
    )
    animal_subjects = models.CharField(
        "IACUC (Animal Research)",
        max_length=4,
        choices=choices.BINARY_CHOICES,
    )
    animal_subjects_detail = models.TextField(
        verbose_name="",
        null=True,
        blank=True,
        help_text="Detail any IACUC approval and submission dates",
    )
    admin_comments = models.TextField(
        null=True,
        blank=True,
        help_text="""
            Provide any administrative comments that you might want
            others to consider.
        """,
    )
    disclosure_assurance = models.BooleanField(default=False)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_impact'

    def title(self):
        """Obtain the proposal title from foreign key Proposal."""
        return self.proposal.title

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-impact/'


class ProposalBudget(models.Model):
    """Proposal budget data."""

    # meta
    created_at = models.DateTimeField(
        'Date Created', auto_now_add=True,
    )
    updated_at = models.DateTimeField(
        'Date Updated', auto_now=True,
    )
    proposal = models.OneToOneField(
        Proposal,
        editable=False,
        related_name='budget',
        on_delete=models.CASCADE,
    )
    # Costs and totals
    total = models.DecimalField(
        "Total Program Cost",
        decimal_places=2,
        max_digits=16,
        help_text="Provide the total amount budgeted for this project",
        null=True,
        blank=True,
    )
    total_funding = models.DecimalField(
        "Provide the total funds requested from this grantor",
        decimal_places=2,
        max_digits=16,
        help_text="Provide the total amount of the funding request",
        null=True,
        blank=True,
    )
    plan_b = models.TextField(
        "Briefly describe your plan for this project if not awarded this grant",
        help_text="~200 words",
    )
    # Files
    budget_final = models.FileField(
        "Final Budget for Review",
        upload_to=upload_to_path,
        validators=FILE_VALIDATORS,
        max_length=512,
        help_text="PDF or Excel Format Only",
    )
    budget_justification_final = models.FileField(
        "Final Budget Justification for Review",
        upload_to=upload_to_path,
        validators=FILE_VALIDATORS,
        max_length=512,
        help_text="PDF or Excel Format Only",
        null=True,
        blank=True,
    )

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_budget'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-budget/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}".format(self.proposal.title)


class ProposalBudgetFunding(models.Model):
    """Proposal budget funding data."""

    # meta
    created_at = models.DateTimeField(
        'Date Created', auto_now_add=True,
    )
    updated_at = models.DateTimeField(
        'Date Updated', auto_now=True,
    )
    budget = models.ForeignKey(
        ProposalBudget,
        editable=False,
        related_name='funding',
        on_delete=models.CASCADE,
    )
    amount = models.DecimalField(
        "Amount/Currency",
        decimal_places=2,
        max_digits=16,
        null=True,
        blank=True,
    )
    source = models.CharField(
        "Describe Other Sources of Funding for the Project",
        max_length=24,
        choices=choices.BUDGET_FUNDING_SOURCE,
    )
    status = models.CharField(
        "Status of these Funds",
        max_length=24,
        choices=choices.BUDGET_FUNDING_STATUS,
    )

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_budget_funding'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-budget-funding/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}".format(self.budget.proposal.title)


class ProposalDocument(models.Model):
    """Proposal supporting documents."""

    # meta
    created_at = models.DateTimeField(
        "Date Created",
        auto_now_add=True,
    )
    proposal = models.ForeignKey(
        Proposal,
        editable=False,
        related_name='documents',
        on_delete=models.CASCADE,
    )
    name = models.CharField(
        "Name or short description of the file",
        max_length=128,
        null=True,
        blank=True,
    )
    phile = models.FileField(
        "Supporting Document",
        upload_to=upload_to_path,
        validators=FILE_VALIDATORS,
        max_length=512,
        help_text="PDF or Excel Format Only",
        null=True,
        blank=True,
    )
    tags = TaggableManager(blank=True)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['-created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_document'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-document/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}: {1}".format(self.name, self.proposal.title)


class ProposalContact(models.Model):
    """Proposal contact data."""

    created_at = models.DateTimeField(
        "Date Created",
        auto_now_add=True,
    )
    proposal = models.ForeignKey(
        Proposal,
        editable=False,
        related_name='contact',
        on_delete=models.CASCADE,
    )
    name = models.CharField(
        max_length=128,
        null=True,
        blank=True,
    )
    institution = models.CharField(
        max_length=128,
        null=True,
        blank=True,
    )
    email = models.EmailField(
        "Your email address",
        max_length=128,
        null=True,
        blank=True,
    )
    tags = TaggableManager(blank=True)

    class Meta:
        """Attributes about the data model and admin options."""

        ordering = ['created_at']
        get_latest_by = 'created_at'
        db_table = 'core_proposal_contact'

    def get_slug(self):
        """Build the URL slug."""
        return 'proposal-contact/'

    def __unicode__(self):
        """Build the default value."""
        return "{0}: {1}".format(self.name, self.institution)


class ProposalApprover(models.Model):
    """Additional folks who need to approve a proposal."""

    user = models.ForeignKey(
        User,
        related_name='approver_user',
        on_delete=models.CASCADE,
    )
    proposal = models.ForeignKey(
        Proposal,
        related_name='approvers',
        on_delete=models.CASCADE,
    )
    # this field is not in use at the moment but i suspect
    # OSP will want to reactivate it in the future
    steps = models.CharField(
        max_length=4,
        default='3',
        choices=choices.PROPOSAL_STEPS_CHOICES,
    )
    replace = models.CharField(
        max_length=24,
        default='level3',
        choices=choices.APPROVAL_LEVEL_CHOICES,
        null=True,
        blank=True,
    )
    step1 = models.BooleanField(default=True)
    step2 = models.BooleanField(default=True)

    class Meta:
        """Attributes about the data model and admin options."""

        db_table = 'core_proposal_approver'

    def first_name(self):
        """Return the approver's first name."""
        return self.user.first_name

    def last_name(self):
        """Return the approver's last name."""
        return self.user.last_name

    def email(self):
        """Return the approver's email."""
        return self.user.email

    def title(self):
        """Return the proposal title."""
        return self.proposal.title
# -*- coding: utf-8 -*- Data models. Return choices for m2m field in ProposalImpact data model. Choices for model and form fields that accept for multiple values. Do you want the field to be visable on the public submission form? Attributes about the data model and admin options. Default data for display. Proposal to pursue funding. # meta # Division Dean or Department VP approval # anyone in the workflow decline the proposal at this point # set to True when steps 1 & 2 have been approved by all and # post_save signal sends email to OSP # PI has submitted proposal for final approval # OSP will close a proposal when they have determined it is # over so that the PI can resubmit if they so choose # signifies that it has been reopened # signifies that it has been awarded funds # Basic Proposal Elements <ul class='block'>             <li>               <b>New</b>: never submitted this proposal to this agency before             </li>             <li>               <b>Revised</b>: per Funder Request: update of submitted proposal,               because funder requested changes             </li>             <li>               <b>Re-Submission</b>: submitted proposal in prior round of               funding, re-submitting proposal for new round             </li>             </ul> # Investigator Information NOTE "Co-Principal Investigators & Associated Institution"     are ProposalContact() Foreign Key relationships.     Name, Instituion fields [limit 5] # Project Overview Provide a brief description of your proposed project.             How does your project help further Carthage's mission?             Describe any partnerships or collaborative relationships             needed to execute the project. # Project Funding / Budget Overview Describe your funding plan:              <ul>                 <li>                     What types of expenses do you expect to have                     (summer salary, travel, student research dollars,                     admin support, materials and supplies, equipment,                     etc...)?                 </li>                 <li>                     Will this grant provide new funds for a new project                     or new funds for an existing project? Will the grant                     provide any budget relief for Carthage? Describe.                 </li>                 <li>                     Will other gift or grant dollars be used to support                     this project? Describe.                 </li>                     What, if any financial commitments                     (match/ cost-share/ in-kind will Carthage need to                     make if awarded these funds?                 </li>                 <li>Does the grant allow for indirect costs? </li>             </ul> # additional comments # administrative comments Provide any administrative comments that you might want             others to consider. Attributes about the data model and admin options. Default data for display. Returns the FQDN URL. Returns the URL for updating the proposal. Returns the proposal slug. What can the user access in terms of viewing & approval process. # in_group includes an exception for superusers # dean or chair? # Dean? # VP for Business? # Provost? # provost might be an adhoc approver # Superuser? # Ad-hoc approver? # at the moment, we assume all approvers will be responsible for # step 1 AND step 2. in the future, i suspect that this might change. Check if step 1 has been approved. # Dean or Department VP Check if step 2 has been approved. Check if VP for Business (level2) & Provost (level1) can approve. Proposal impact data. # meta # status # Division Dean # VP for Business # Provost # institutional impacts If you checked any of the boxes above, please provide additional             details. Will this project require that your department hire someone             to teach the courses you are scheduled to teach             or any other type of course relief? # begin questions after instructional text Provide any administrative comments that you might want             others to consider. Attributes about the data model and admin options. Obtain the proposal title from foreign key Proposal. Build the URL slug. Proposal budget data. # meta # Costs and totals # Files Attributes about the data model and admin options. Build the URL slug. Build the default value. Proposal budget funding data. # meta Attributes about the data model and admin options. Build the URL slug. Build the default value. Proposal supporting documents. # meta Attributes about the data model and admin options. Build the URL slug. Build the default value. Proposal contact data. Attributes about the data model and admin options. Build the URL slug. Build the default value. Additional folks who need to approve a proposal. # this field is not in use at the moment but i suspect # OSP will want to reactivate it in the future Attributes about the data model and admin options. Return the approver's first name. Return the approver's last name. Return the approver's email. Return the proposal title.