blob: 6629e203eb4b4e8540ab111fbc711b5a872fcb3b [file] [log] [blame]
# Copyright 2014 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This module contains the proposal related models."""
from google.appengine.ext import ndb
from google.appengine.ext.ndb import msgprop
from melange.appengine import db as melange_db
from protorpc import messages
class Visibility(messages.Enum):
"""Class that enumerates possible visibility types for proposals."""
#: Content of the proposal is visible to everyone.
PUBLIC = 1
#: Content of the proposal is visible to members of the organization
#: to which the proposal has been submitted.
ORG_MEMBER = 2
#: Content of the proposal is visible to all registered students of the
#: current program.
STUDENT = 3
class Status(messages.Enum):
"""Class that enumerates possible statuses of proposals."""
#: The proposal has been submitted to the program.
PENDING = 1
#: The proposal has been accepted into the program and has been turned
#: into a student project.
ACCEPTED = 2
#: This proposal has been rejected.
REJECTED = 3
# TODO(daniel): this status should be removed.
#: The proposal has been ignored by the organization.
#: Please DO NOT USE.
IGNORED = 4
#: The proposal has been withdrawn by the student.
WITHDRAWN = 5
class Score(ndb.Model):
"""Model to store information on a single score for the proposal."""
#: Required field storing a reference to the profile who is an author
#: if the score.
author = ndb.KeyProperty(required=True)
#: Required field storing numeric value associated with the score, i.e.
#: number of points given to the proposal.
value = ndb.IntegerProperty(required=True)
class Proposal(ndb.Model):
"""Model that represents a proposal that is submitted to an organization
by a student participating in the program.
Parent:
melange.models.profile.Profile (of the student who owns the proposal)
"""
#: Required field indicating the title of the proposal.
title = ndb.StringProperty(required=True)
#: Required field storing a short description of the proposal.
abstract = ndb.TextProperty(required=True)
#: Optional field storing URL linking to additional resources
#: associated with the proposal.
additional_info = ndb.StringProperty(validator=melange_db.link_validator)
#: Required field determining who is eligible to see the proposal.
visibility = msgprop.EnumProperty(
Visibility, required=True, default=Visibility.ORG_MEMBER)
#: List of mentors associated with the proposal. They will be assigned as
#: mentors if the proposal is turned into a project.
mentors = ndb.KeyProperty(repeated=True)
#: Field determining whether the proposal has currently at least one
#: mentor assigned.
has_mentor = ndb.ComputedProperty(lambda self: bool(self.mentors))
#: List of organization members who are possible mentors for the proposal.
possible_mentors = ndb.KeyProperty(repeated=True)
#: List of scores that have been given to the proposal.
scores = ndb.StructuredProperty(Score, repeated=True)
#: Sum of all scores for this proposal
total_score = ndb.ComputedProperty(
lambda self: sum(score.value for score in self.scores))
#: Field telling whether the student is eligible to edit the proposal
#: after the proposal submission deadline.
is_editable_post_deadline = ndb.BooleanProperty(default=False)
#: Field telling whether the proposal should be accepted and turned
#: into a student project.
accept_as_project = ndb.BooleanProperty(default=False)
#: Field storing status of the proposal.
status = msgprop.EnumProperty(Status, required=True, default=Status.PENDING)
#: Field telling whether the proposal has been ignored by the organization.
is_ignored = ndb.BooleanProperty(default=False)
#: Field storing the organization to which the proposal has been submitted.
organization = ndb.KeyProperty(required=True)
#: Field storing the program for which the proposal has been submitted.
program = ndb.KeyProperty(required=True)
#: Field storing the date when the proposal was initially created.
created_on = ndb.DateTimeProperty(required=True, auto_now_add=True)
#: Field storing the date when the proposal was modified for the last time.
modified_on = ndb.DateTimeProperty(required=True, auto_now=True)
#: Values for additional fields (defined by the organization) for
#: this proposal.
extra_data = ndb.JsonProperty()
#: Field storing the key of 'ProposalRevision' entity which is the latest
#: revision of this proposal. This property is required but it has been not
#: specified here. This is because of the cyclic reference that occurs when
#: a user submits the proposal for the first time. The cyclic reference
#: occurs between the Proposal and ProposalRevision entities, where the
#: latter requires the former as its parent, and the former requires the
#: key of the latter for this property.
latest_revision = ndb.KeyProperty()
@property
def content(self):
"""Returns actual content of the proposal."""
return self.latest_revision.get().content
@property
def count_scores(self):
"""Returns number of scores for this proposal."""
return len(self.scores)
@property
def average_score(self):
"""Returns average score for this proposal. If no scores are currently
defined, None is returned
"""
if not self.scores:
return None
else:
return float(self.total_score) / float(len(self.scores))
@property
def median_score(self):
"""Returns median score for this proposal."""
if not self.scores:
return None
else:
sorted_values = sorted(score.value for score in self.scores)
index = len(sorted_values) // 2
if len(sorted_values) % 2 == 0:
return (sorted_values[index - 1] + sorted_values[index]) / 2.0
else:
return sorted_values[index]
class ProposalRevision(ndb.Model):
"""Model that represents proposal revision.
A proposal revision represents an edit of the proposal that is created when a
proposal entity is created or when a proposal is resubmitted to an
organization by a student participating in the program.
Parent:
summerofcode.models.proposal.Proposal (of the proposal revision which is stored)
"""
#: Field storing the text body of the proposal content.
content = ndb.TextProperty(required=True)
#: Field storing the date when the proposal content was created.
created_on = ndb.DateTimeProperty(required=True, auto_now_add=True)
@property
def revision(self):
"""Returns value indicating the revision number of the proposal revision."""
return int(self.key.id())