Beispiel #1
0
class UserReinvestment(models.Model):
    """
    Model representing a single, "contact point" for a
    reinvestment by user himself.

    User only can do reinvestment if he is on reinvestment period (usually before
    15th day of a running month).

    And An UserReinvestment cannot be created if there are insufficient funds.

    ::Signals::
    pre_init
        Raises a NotEnoughFundingException before __init__ if there are not
        enough funds for this UserReinvestment or not in reinvestment period.
    pre_save
        We'll cap the investment here, by monthly allocation and founding_goal
    post_save
        Send to payment
    """
    amount = models.FloatField()
    user = models.ForeignKey('base.RevolvUserProfile')
    project = models.ForeignKey("project.Project")

    created_at = models.DateTimeField(auto_now_add=True)

    objects = UserReinvestmentManager()
    factories = ImportProxy("revolv.payments.factories", "UserReinvestmentFactory")
Beispiel #2
0
class ProjectUpdate(models.Model):
    factories = ImportProxy("revolv.project.factories",
                            "ProjectUpdateFactories")
    update_text = RichTextField(
        'Update content',
        help_text="What should be the content of the update?")

    date = models.DateField('Date of update creation',
                            help_text="What time was the update created?",
                            auto_now_add=True)

    project = models.ForeignKey(Project, related_name="updates")
Beispiel #3
0
class AdminReinvestment(models.Model):
    """
    Model representing a single, admin-controlled "contact point" for a
    reinvestment into an ongoing RE-volv project.

    RE-volv usually only reinvests into a project at its launch, but it is still
    possible for an admin to put in a reinvestment at any time.

    Creating an AdminReinvestment automatically pools money from users with a
    non-zero pool of reinvestable money, prioritizing users that have a
    preference for the Category of the project being reinvested into. (A user's
    pool of reinvestable money consists of the sum of unspent repayment
    fragments to that user.)

    We generate a Payment of type 'reinvestment_fragment' for each user that we
    pooled money from with the amount of money that we pooled from that user,
    and also decrement that user's pool of reinvestable money.

    An AdminReinvestment cannot be created if there are insufficient funds.

    We need a single "contact point" representing a reinvestment so that admins
    have the ability to "revoke" a reinvestment, if it was entered falsely.
    Deleting an AdminReinvestment also deletes any "reinvestment_fragment"-type
    Payments associated with it, effectively erasing any trace of the
    reinvestment.

    ::Signals::
    pre_init
        Raises a NotEnoughFundingException before __init__ if there are not
        enough funds for this AdminReinvestment.
    post_save
        When an AdminReinvestment is saved, we pool as many donors as we need to
        fund the reinvestment, prioritizing users that have a preference for the
        Category of the project begin invested into. We only consider users that
        have a non-zero pool of investable money.
        !!! TODO: actually prioritize by Category
    """
    amount = models.FloatField()
    admin = models.ForeignKey('base.RevolvUserProfile')
    project = models.ForeignKey("project.Project")

    created_at = models.DateTimeField(auto_now_add=True)

    objects = AdminReinvestmentManager()
    factories = ImportProxy("revolv.payments.factories",
                            "AdminReinvestmentFactories")

    def __unicode__(self):
        return '%s for %s' % (self.amount, self.project)
Beispiel #4
0
class Payment(models.Model):
    """
    Abstraction indicating one particular payment.

    A Payment represents only money flowing from a user to a project, not the
    other way around. RepaymentFragment is a separate model representing the
    reverse.

    ::Signals::
    post_save
        If the payment is organic, we add this payment's user as a donor
        to the related project. If the payment is a reinvestment_fragment, we decrement
        the reinvest_pool in the related user.
    pre_delete
        If the payment is organic, we remove this payment's user as a donor to
        the related project if he has no other payments to that project.  If the
        payment is a reinvestment_fragment, we decrement the reinvest_pool in the related
        user. Reinvestment money originates from repayments made by
        already completed projects.
    """
    user = models.ForeignKey('base.RevolvUserProfile', blank=True, null=True)
    project = models.ForeignKey("project.Project")

    entrant = models.ForeignKey('base.RevolvUserProfile',
                                related_name='entrant')
    payment_type = models.ForeignKey(PaymentType)
    created_at = models.DateTimeField(auto_now_add=True)

    admin_reinvestment = models.ForeignKey(AdminReinvestment,
                                           blank=True,
                                           null=True)
    solar_seed_monthly = models.ForeignKey(SolarSeedFund,
                                           blank=True,
                                           null=True)
    user_reinvestment = models.ForeignKey(UserReinvestment,
                                          blank=True,
                                          null=True)
    tip = models.ForeignKey('payments.Tip', blank=True, null=True)
    amount = models.FloatField()

    objects = PaymentManager()
    factories = ImportProxy("revolv.payments.factories", "PaymentFactories")

    @property
    def is_organic(self):
        return self.user == self.entrant

    def __unicode__(self):
        return '%s from %s for %s' % (self.amount, self.user, self.project)
Beispiel #5
0
class AdminRepayment(models.Model):
    """
    Model representing a single, admin-controlled "contact point" for a
    repayment from a completed RE-volv project.

    When a RE-volv project makes a repayment, we create a representative
    AdminRepayment instance in our database. Creating an AdminRepayment
    automatically generates a RepaymentFragment for each RevolvUserProfile who
    organically donated to the project.  For user U, project P, and
    AdminRepayment R, the "amount" of the U's RepaymentFragment will be:

        ((U's donation to P) / (Total organic donations to P)) * R.amount

    Generating a RepaymentFragment for a user increases that user's pool of
    reinvestable money.

    We need a single "contact point" representing a repayment so that admins
    have the ability to "revoke" a repayment, if it was entered falsely.
    Deleting an AdminRepayment also deletes any RepaymentFragments associated
    with it, effectively erasing any trace of the repayment.

    ::Signals::
    pre_init
        Make sure that related project is indeed complete, else throw a
        ProjectNotCompleteException to disallow instantiation of an invalid
        AdminRepayment.
    post_save
        When an AdminRepayment is saved, a RepaymentFragment is generated for
        all donors to a project, each weighted by that donor's proportion of the
        contribution to the project.
    """
    amount = models.FloatField()
    admin = models.ForeignKey('base.RevolvUserProfile')
    project = models.ForeignKey("project.Project")

    created_at = models.DateTimeField(auto_now_add=True)

    objects = AdminRepaymentManager()
    factories = ImportProxy("revolv.payments.factories",
                            "AdminRepaymentFactories")

    def __unicode__(self):
        return '%s for %s' % (self.amount, self.project)
Beispiel #6
0
class ProjectMontlyRepaymentConfig(models.Model):
    """
    A Model contains configuration distribution of repayment.

    Repayment will be spilt by 2: for Solar Seed fund(SSF) and for RE-volv overhead.
    We'll used value on SSF for calculating fund to reinvestmentm each month
    """
    SOLAR_SEED_FUND = 'SSF'
    REVOLVE_OVERHEAD = 'REV'
    REPAYMENT_TYPE_CHOICES = ((SOLAR_SEED_FUND, 'Solar Seed Fund'),
                              (REVOLVE_OVERHEAD, 'RE-volv Overhead'))

    project = models.ForeignKey("project.Project")
    year = models.PositiveSmallIntegerField(default=date.today().year)
    repayment_type = models.CharField(max_length=3,
                                      choices=REPAYMENT_TYPE_CHOICES)
    amount = models.FloatField()
    factories = ImportProxy('revolv.payments.factories',
                            'ProjectMontlyRepaymentConfigFactory')
Beispiel #7
0
class Category(models.Model):
    """
    Categories that a project is associated with. Categories are predefined,
    and as of now, loaded through fixtures.
    """
    HEALTH = 'Health'
    ARTS = 'Arts'
    FAITH = 'Faith'
    EDUCATION = 'Education'
    COMMUNITY = 'Community'

    valid_categories = [HEALTH, ARTS, FAITH, EDUCATION, COMMUNITY]

    factories = ImportProxy("revolv.project.factories", "CategoryFactories")

    title = models.CharField(max_length=50, unique=True)
    projects = models.ManyToManyField(Project)

    def __unicode__(self):
        return self.title
Beispiel #8
0
class ProjectMontlyRepaymentConfig(models.Model):
    import calendar
    """
    A Model contains configuration distribution of repayment.

    Repayment will be spilt by 2: for Solar Seed fund(SSF) and for RE-volv overhead.
    We'll used value on SSF for calculating fund to reinvestmentm each month
    """
    SOLAR_SEED_FUND = 'SSF'
    REVOLVE_OVERHEAD = 'REV'
    REPAYMENT_TYPE_CHOICES = ((SOLAR_SEED_FUND, 'Solar Seed Fund'), (REVOLVE_OVERHEAD, 'RE-volv Overhead'))

    project = models.ForeignKey("project.Project")
    year = models.IntegerField(max_length=4, default=date.today().year)
    month = models.CharField(max_length=25, default=calendar.month_name[date.today().month])
    repayment_type = models.CharField(max_length=3, choices=REPAYMENT_TYPE_CHOICES, blank=True)
    amount = models.FloatField()
    factories = ImportProxy('revolv.payments.factories', 'ProjectMontlyRepaymentConfigFactory')

    def __unicode__(self):
        return '%s %s in %s %s for %s' % (self.repayment_type, self.amount, self.month, self.year, self.project)
Beispiel #9
0
class RepaymentFragment(models.Model):
    """
    Abstraction for a fragment of a repayment from a project that belongs to a
    particular RevolvUserProfile.

    A RepaymentFragment represents the proportional amount of money that the
    associated user is repayed for an AdminRepayment to a project. In other
    words, for user U, project P, and AdminRepayment R, the amount of the
    RepaymentFragment for U will be:

        ((U's donation to P) / (Total organic donations to P)) * R.amount

    When a RepaymentFragment is generated/deleted, we automatically
    increment/decrement the pool of reinvestable money in the related user.

    RepaymentFragments are generated automatically when an AdminRepayment is
    created.  RepaymentFragments should never be created manually!

    ::Signals::
    post_save
        When a RepaymentFragment is saved, we increment the reinvest_pool in the
        related user.
    pre_delete
        Before a RepaymentFragment is deleted, we decrement the reinvest_pool in
        the related user.
    """
    user = models.ForeignKey('base.RevolvUserProfile')
    project = models.ForeignKey("project.Project")
    admin_repayment = models.ForeignKey(AdminRepayment)

    amount = models.FloatField()

    created_at = models.DateTimeField(auto_now_add=True)

    objects = RepaymentFragmentManager()
    factories = ImportProxy("revolv.payments.factories",
                            "RepaymentFragmentFactories")

    def __unicode__(self):
        return '%s to %s for %s' % (self.amount, self.user, self.project)
Beispiel #10
0
class Project(models.Model):
    """
    Project model. Stores basic metadata, information about the project,
    donations, energy impact, goals, and info about the organization.

    Note about project statuses: there are five kinds of statuses that a
    project can have, and we show projects to different users in different
    ways based on their status.

    When an ambassador or admin first creates a project, it becomes DRAFTED,
    which means that it's a draft and can be edited, but is not in a complete
    state yet (description may need editing, etc). Eventualy the ambassador
    can propose the project for review from the admins, at which time it becomes
    PROPOSED. A proposed project is viewable by admins in their dashboard, as
    well as by the ambassadors that created it.

    When an admin approves a project, it becomes STAGED, which means it is ready
    to go but is not active yet, and as such is not viewable by the public. Staged
    projects are also visible to all admins in their dashboards. When it's time
    for the project to go live and start accepting donations, the admin can mark
    it as ACTIVE, which means it will actually be public and people can donate to
    it. When a project is done, the admin can mark it as COMPLETED, at which point
    it will stop accepting donations and start using repayments.
    """
    ACTIVE = 'AC'
    STAGED = 'ST'
    PROPOSED = 'PR'
    COMPLETED = 'CO'
    DRAFTED = 'DR'
    PROJECT_STATUS_CHOICES = (
        (ACTIVE, 'Active'),
        (STAGED, 'Staged'),
        (PROPOSED, 'Proposed'),
        (COMPLETED, 'Completed'),
        (DRAFTED, 'Drafted'),
    )
    LESS_THAN_ONE_DAY_LEFT_STATEMENT = "only hours left"
    NO_DAYS_LEFT_STATEMENT = "deadline reached"

    funding_goal = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        help_text='How much do you aim to raise for this project?')
    total_kwh_value = models.DecimalField(
        max_digits=15,
        decimal_places=2,
        default=0,
        help_text=
        'How much is the total kWH value for 25 years to this project?')
    project_url = models.CharField(
        max_length=255,
        null=True,
        blank=False,
        help_text='How to show project url for this project?')
    title = models.CharField(
        max_length=255, help_text='How would you like to title this project?')
    tagline = models.CharField(
        max_length=100,
        null=True,
        blank=False,
        help_text=
        'Select a short tag line that describes this project. (No more than 100 characters.)'
    )
    video_url = models.URLField(
        'Video URL',
        max_length=255,
        blank=False,
        help_text='Link to a Youtube video about the project or community.',
    )
    # power output of array in kilowatts
    impact_power = models.FloatField(
        'Expected Killowatt Output',
        help_text=
        'What is the expected output in killowatts of the proposed solar array?'
    )
    # solar log graphics url
    solar_url = models.URLField(
        'Solar Log Graphics URL',
        max_length=255,
        blank=True,
        help_text=
        'This can be found by going to http://home.solarlog-web.net/, going to the \
            solar log profile for your site, and clicking on the Graphics sub-page. Copy and paste \
            the URL in the address bar into here.')
    location = models.CharField(
        'Organization Address',
        max_length=255,
        help_text=
        'What is the address of the organization where the solar panels will be installed?'
    )
    # latitude and longitude of the organization location
    location_latitude = models.DecimalField(max_digits=17,
                                            decimal_places=14,
                                            default=0.0)
    location_longitude = models.DecimalField(max_digits=17,
                                             decimal_places=14,
                                             default=0.0)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    end_date = models.DateField(
        help_text='When will this crowdfunding project end?')
    # the start date of a project is whenever the project becomes live,
    # so we have to set it dynamically. Accordingly, the start_date
    # field is blank=True.
    start_date = models.DateField(blank=True, null=True)
    project_status = models.CharField(max_length=2,
                                      choices=PROJECT_STATUS_CHOICES,
                                      default=DRAFTED)
    cover_photo = ProcessedImageField(
        upload_to='covers/',
        processors=[ResizeToFill(1200, 500)],
        format='JPEG',
        options={'quality': 80},
        default=None,
        help_text=
        'Choose a beautiful high resolution image to represent this project.',
        blank=False,
    )
    preview_photo = ImageSpecField(
        source='cover_photo',
        processors=[ResizeToFill(400, 300)],
        format='JPEG',
        options={'quality': 80},
    )
    org_start_date = models.DateField(
        'Organization Founding Date',
        blank=True,
        null=True,
        help_text='When was the organization being helped established?')

    org_name = models.CharField(
        'Organization Name',
        max_length=255,
        help_text='What is the name of the organization being helped?')

    people_affected = models.PositiveIntegerField(
        default=0,
        help_text='How many people will be impacted by this project?')

    mission_statement = models.TextField(
        'Organization Mission',
        help_text=
        'What is the mission statement of the organization being helped by this project?',
    )

    org_about = models.TextField(
        'Organization Description',
        help_text=
        'Elaborate more about the organization, what it does, who it serves, etc.'
    )

    description = RichTextField(
        'Project description',
        help_text=
        'This is the body of content that shows up on the project page.')

    donors = models.ManyToManyField(RevolvUserProfile, blank=True)

    created_by_user = models.ForeignKey(RevolvUserProfile,
                                        related_name='created_by_user')

    ambassadors = models.ManyToManyField(RevolvUserProfile,
                                         related_name='ambassadors',
                                         null=True)

    # energy produced in kilowatt hours
    actual_energy = models.FloatField(default=0.0)
    internal_rate_return = models.DecimalField(
        'Internal Rate of Return',
        max_digits=6,
        decimal_places=3,
        default=0.0,
        help_text='The internal rate of return for this project.')

    # solar data csv files
    daily_solar_data = models.FileField(blank=True,
                                        null=True,
                                        upload_to="projects/daily/")
    monthly_solar_data = models.FileField(blank=True,
                                          null=True,
                                          upload_to="projects/monthly/")
    annual_solar_data = models.FileField(blank=True,
                                         null=True,
                                         upload_to="projects/annual/")

    monthly_reinvestment_cap = models.FloatField(blank=True, default=0.0)
    is_paid_off = models.BooleanField(blank=True, default=False)

    objects = ProjectManager()
    factories = ImportProxy("revolv.project.factories", "ProjectFactories")

    def has_owner(self, creator):
        return self.created_by_user == creator

    def approve_project(self):
        self.project_status = Project.ACTIVE
        if self.start_date is None:
            self.start_date = datetime.date.today()
        self.save()
        return self

    # TODO(noah): change this verbiage. we should probably call the STAGED -> ACTIVE
    # transition "activate_project" and the PROPOSED -> STAGED transition "approve_project"
    # instead.
    def stage_project(self):
        self.project_status = Project.STAGED
        self.save()
        return self

    def unapprove_project(self):
        self.project_status = Project.STAGED
        self.start_date = None
        self.save()
        return self

    def propose_project(self):
        self.project_status = Project.PROPOSED
        self.save()
        return self

    def deny_project(self):
        self.project_status = Project.DRAFTED
        self.save()
        return self

    def complete_project(self):
        self.project_status = Project.COMPLETED
        self.save()
        return self

    def mark_as_incomplete_project(self):
        self.project_status = Project.ACTIVE
        self.save()
        return self

    def update_categories(self, category_list):
        """ Updates the categories list for the project.

        :category_list The list of categories in the submitted form
        """
        # Clears all the existing categories
        self.category_set.clear()

        # Adds the list of categories to the project
        for category in category_list:
            category_object = Category.objects.get(title=category)
            self.category_set.add(category_object)

    def get_absolute_url(self):
        return reverse("project:view", kwargs={"title": str(self.project_url)})

    def get_organic_donations(self):
        return self.payment_set.exclude(user__isnull=True).filter(
            entrant__pk=models.F('user__pk'))

    def proportion_donated(self, user):
        """
        :return:
            The proportion that this user has organically donated to this
            project as a float in the range [0, 1] (inclusive)
        """
        user_donation = Payment.objects.donations(
            project=self, user=user, organic=True).aggregate(
                models.Sum('amount'))['amount__sum'] or 0.0
        prop = user_donation / self.amount_donated_organically
        assert 0 <= prop <= 1, "proportion_donated is incorrect!"
        return prop

    @property
    def amount_donated_organically(self):
        """
        :return: the current total amount that has been organically donated to
        this project, as a float.
        """
        return self.get_organic_donations().aggregate(
            models.Sum('amount'))["amount__sum"] or 0.0

    @property
    def location_street(self):
        """
        :return: a string of the street name of the location of this project.
        If the project location is malformed, will return an empty string.
        """
        try:
            return self.location.split(',')[0]
        except IndexError:
            return ""

    @property
    def location_city_state_zip(self):
        """
        :return: a string of the city, state, and zip code of the location of this project.
        If the project location is malformed, will return an empty string.
        """
        try:
            pieces = self.location.split(',')
            if len(pieces) >= 3:
                return pieces[1] + "," + pieces[2]
            elif len(pieces) == 2:
                return pieces[1]
            return pieces[0]
        except IndexError:
            return ""

    @property
    def amount_donated(self):
        """
        :return: the current total amount that has been donated to this project,
            as a float.
        """
        return self.payment_set.aggregate(
            models.Sum('amount'))["amount__sum"] or 0.0

    @property
    def amount_left(self):
        """
        :return: the current amount of money needed for this project to
            reach its goal, as a float.
        """
        amt_left = float(self.funding_goal) - self.amount_donated
        if amt_left < 0:
            return 0.0
        return amt_left

    @property
    def amount_repaid(self):
        """
        :return: the current amount of money repaid by the project to RE-volv.
        """
        return self.adminrepayment_set.aggregate(
            models.Sum('amount'))["amount__sum"] or 0.0

    @property
    def total_amount_to_be_repaid(self):
        """
        :return: the total amount of money to be repaid by the project to RE-volv.
        """
        # TODO (https://github.com/calblueprint/revolv/issues/291): Actually
        # calculate this amount based off of interest, but using the project's
        # funding goal is sufficient for now.
        return self.funding_goal

    @property
    def rounded_amount_left(self):
        """
        :return: The amount needed to complete this project, floored to the nearest
            dollar.

        Note: if for some reason the amount left is negative, this will perform a
        ceiling operation instead of a floor, but that should never happen.
        """
        return int(self.amount_left)

    @property
    def partial_completeness(self):
        """
        :return: a float between 0 and 1, representing the completeness of this
            project with respect to its goal (1 if exactly the goal amount, or
            more, has been donated, 0 if nothing has been donated).
        """
        ratio = self.amount_donated / float(self.funding_goal)
        return min(ratio, 1.0)

    @property
    def percent_complete(self):
        """
        :return: a floored int between 0 and 100, representing the completeness of this
            project with respect to its goal (100 if exactly the goal amount, or
            more, has been donated, 0 if nothing has been donated).
        """
        return int(self.partial_completeness * 100)

    def partial_completeness_as_js(self):
        return unicode(self.partial_completeness)

    @property
    def percent_repaid(self):
        """
        :return: a floored int between 0 and 100, representing the amount repaid
        in respect to its repayment goal (100 if exactly the goal amount, or
        more, has been donated, 0 if nothing has been donated).
        """
        return int(self.partial_repayment * 100)

    @property
    def partial_repayment(self):
        """
        :return: a float between 0 and 1, representing the repayment progress
        of this project with respect to the repayment goal (1 if exactly the
        goal amount, or more, has been donated, 0 if nothing has been donated).
        """
        ratio = self.amount_repaid / float(self.total_amount_to_be_repaid)
        return min(ratio, 1.0)

    def partial_repayment_as_js(self):
        return unicode(self.partial_repayment)

    @property
    def total_days(self):
        """
        :return the total length of the campaign of this project,
        or None if the project hasn't started yet.

        Note: if a project's campaign starts and ends on the same day, it is
        defined to be one day long, not zero days long.
        """
        if self.start_date is None:
            return None
        return max((self.end_date - self.start_date).days + 1, 0)

    @property
    def days_until_end(self):
        """
        :return: the difference between today and the end date of this project.
        May be negative.
        """
        return (self.end_date - datetime.date.today()).days

    @property
    def days_so_far(self):
        """
        :return: the integer number of days that have passed since
        this project's campaign began, or None if it has not started
        yet.
        """
        if self.start_date is None:
            return None
        difference = (datetime.date.today() - self.start_date).days
        if difference < 0:
            return 0
        if difference > self.total_days:
            return self.total_days
        return difference

    @property
    def days_left(self):
        """
        :return: the integer number of days until the end of this project,
        or 0 if the project's campaign has finished.
        """
        return max(self.days_until_end, 0)

    def formatted_days_left(self):
        """
        :return: the number of days left in this project's campaign, formatted
        according to how many days left there are. This includes a default message
        when there are 0 days left instead of just saying "0".

        TODO: this should probably be moved to the template logic.
        """
        days_left = self.days_until_end
        if days_left == 1:
            return "1 day left"
        if days_left == 0:
            return self.LESS_THAN_ONE_DAY_LEFT_STATEMENT
        if days_left < 0:
            return self.NO_DAYS_LEFT_STATEMENT
        return unicode(days_left) + " days left"

    @property
    def is_active(self):
        return self.project_status == Project.ACTIVE

    @property
    def is_proposed(self):
        return self.project_status == Project.PROPOSED

    @property
    def is_drafted(self):
        return self.project_status == Project.DRAFTED

    @property
    def is_staged(self):
        return self.project_status == Project.STAGED

    @property
    def is_completed(self):
        return self.project_status == Project.COMPLETED

    @property
    def status_display(self):
        return dict(Project.PROJECT_STATUS_CHOICES)[self.project_status]

    @property
    def categories(self):
        return [category.title for category in self.category_set.all()]

    @property
    def updates(self):
        """
        :return: The set of all ProjectUpdate models associated with this project.
        """
        return self.updates.all()

    @property
    def donation_levels(self):
        """
        :return: The set of all DonationLevel models associated with this project.
        """
        return self.donationlevel_set.all()

    @property
    def statistics(self):
        """
        Return a revolv.project.stats.KilowattStatsAggregator for this project.
        Having this as a property is usefule in templates where we need to display
        statistics about the project (e.g. lbs carbon saved, $ saved, etc).
        """
        return KilowattStatsAggregator.from_project(self)

    def add_update(self, text):
        update = ProjectUpdate(update_text=text, project=self)
        update.save()

    @property
    def reinvest_amount_left(self):
        """
        :return max reinvestment can be receive
        """
        return min(self.amount_left, self.monthly_reinvestment_cap)

    def get_statistic_for_project(self):
        user_impact = 0
        project_funding_total = (int)(self.funding_goal)
        amount_donated = (int)(self.amount_donated)
        project_total_kwh_value = self.total_kwh_value
        total_carbon_avoided = float(project_total_kwh_value) * 1.5
        per_doller_co2_avoided = total_carbon_avoided / project_funding_total
        project_impact = per_doller_co2_avoided * amount_donated
        user_impact += project_impact
        return user_impact

    def paid_off(self):
        """Set the project PAID_OFF flag
        """
        self.is_paid_off = True
        self.save()

    def __unicode__(self):
        return self.title + '-' + self.project_status
Beispiel #11
0
class RevolvUserProfile(FacebookModel):
    """
    A simple wrapper around django-facebook's FacebookModel, which contains
    Facebook information like name, etc. RevolvUserProfile ties a FacebookModel
    and a django auth.User model together, so that we can use both Facebook
    and non-Facebook user profiles.

    Note: there are three main roles that users in the Revolv application can
    occupy: donor, ambassador, and admin.

    Donors are regular users, who can donate to projects and see the impact of
    their donations.

    Ambassadors are users who can donate AND create projects, to be
    approved by the admin. Note: ambassadors are NOT staff with respect to the
    django User model, since we use the is_staff boolean to check whether the
    django CMS toolbar is visible for users.

    Admins are users who can approve and manage projects, AND control whether
    other users are ambassadors or admins themselves. Admins can also donate to
    projects like regular donors can. Every admin's User model has is_staff = True
    in order to see the django-cms toolbar on the homepage.
    """
    objects = RevolvUserProfileManager()
    factories = ImportProxy("revolv.base.factories",
                            "RevolvUserProfileFactories")

    AMBASSADOR_GROUP = "ambassadors"
    ADMIN_GROUP = "administrators"

    user = models.OneToOneField(User)
    subscribed_to_newsletter = models.BooleanField(default=False)

    reinvest_pool = models.FloatField(default=0.0)
    preferred_categories = models.ManyToManyField("project.Category")

    def is_donor(self):
        """Return whether the associated user can donate."""
        return True

    def is_ambassador(self):
        return get_group_by_name(
            self.AMBASSADOR_GROUP) in self.user.groups.all()

    def is_administrator(self):
        return get_group_by_name(self.ADMIN_GROUP) in self.user.groups.all()

    def make_administrator(self):
        self.user.groups.add(get_group_by_name(self.AMBASSADOR_GROUP))
        self.user.groups.add(get_group_by_name(self.ADMIN_GROUP))
        self.user.is_staff = True
        self.user.is_superuser = True
        self.user.save()

    def make_ambassador(self):
        self.user.is_staff = False
        self.user.is_superuser = False
        self.user.groups.remove(get_group_by_name(self.ADMIN_GROUP))
        self.user.groups.add(get_group_by_name(self.AMBASSADOR_GROUP))
        self.user.save()

    def make_donor(self):
        """Take away all the user's permissions."""
        self.user.is_staff = False
        self.user.is_superuser = False
        self.user.groups.remove(get_group_by_name(self.ADMIN_GROUP))
        self.user.groups.remove(get_group_by_name(self.AMBASSADOR_GROUP))
        self.user.save()

    def get_statistic_for_user(self, attr):
        """Calculates a user's individual impact by iterating through all the users payments, calculating
        what fraction of that project comprises of this user's donation, and calculates individual
        user impact using the statistics attribute (a KilowattStatsAggregator) and the fraction."""
        all_payments = Payment.objects.payments(user=self)
        user_impact = 0
        for payment in all_payments:
            project = payment.project
            user_financial_contribution = payment.amount
            project_funding_total = (int)(project.funding_goal)
            project_impact = getattr(project.statistics, attr)
            user_impact_for_project = project_impact * user_financial_contribution * 1.0 / project_funding_total
            user_impact += user_impact_for_project
        return user_impact
Beispiel #12
0
class RevolvUserProfile(FacebookModel):
    """
    A simple wrapper around django-facebook's FacebookModel, which contains
    Facebook information like name, etc. RevolvUserProfile ties a FacebookModel
    and a django auth.User model together, so that we can use both Facebook
    and non-Facebook user profiles.

    Note: there are three main roles that users in the Revolv application can
    occupy: donor, ambassador, and admin.

    Donors are regular users, who can donate to projects and see the impact of
    their donations.

    Ambassadors are users who can donate AND create projects, to be
    approved by the admin. Note: ambassadors are NOT staff with respect to the
    django User model, since we use the is_staff boolean to check whether the
    django CMS toolbar is visible for users.

    Admins are users who can approve and manage projects, AND control whether
    other users are ambassadors or admins themselves. Admins can also donate to
    projects like regular donors can. Every admin's User model has is_staff = True
    in order to see the django-cms toolbar on the homepage.
    """
    objects = RevolvUserProfileManager()
    factories = ImportProxy("revolv.base.factories", "RevolvUserProfileFactories")

    AMBASSADOR_GROUP = "ambassadors"
    ADMIN_GROUP = "administrators"

    user = models.OneToOneField(User)
    subscribed_to_newsletter = models.BooleanField(default=False)
    zipcode = models.CharField(max_length=10, null=True, blank=True, default="")
    subscribed_to_updates = models.BooleanField(default=False)
    subscribed_to_repayment_notifications = models.BooleanField(default=True)

    reinvest_pool = models.FloatField(default=0.0)
    solar_seed_fund_pool = models.FloatField(default=0.0)
    preferred_categories = models.ManyToManyField("project.Category")

    address = models.CharField(max_length=255, null=True, blank=True)

    def is_donor(self):
        """Return whether the associated user can donate."""
        return True

    def is_ambassador(self):
        return get_group_by_name(
            self.AMBASSADOR_GROUP
        ) in self.user.groups.all()

    def is_administrator(self):
        return get_group_by_name(self.ADMIN_GROUP) in self.user.groups.all()

    def make_administrator(self):
        self.user.groups.add(get_group_by_name(self.AMBASSADOR_GROUP))
        self.user.groups.add(get_group_by_name(self.ADMIN_GROUP))
        self.user.is_staff = True
        self.user.is_superuser = True
        self.user.save()

    def make_ambassador(self):
        self.user.is_staff = False
        self.user.is_superuser = False
        self.user.groups.remove(get_group_by_name(self.ADMIN_GROUP))
        self.user.groups.add(get_group_by_name(self.AMBASSADOR_GROUP))
        self.user.save()

    def make_donor(self):
        """Take away all the user's permissions."""
        self.user.is_staff = False
        self.user.is_superuser = False
        self.user.groups.remove(get_group_by_name(self.ADMIN_GROUP))
        self.user.groups.remove(get_group_by_name(self.AMBASSADOR_GROUP))
        self.user.save()

    def user_impact_for_watts(self):
        all_payments = Payment.objects.payments(user=self).exclude(project__isnull=True)
        user_impact = 0
        for payment in all_payments:
            project = payment.project
            if project:
                user_financial_contribution = payment.amount
                project_funding_total = (int)(project.funding_goal)
                expected_KW_Output = project.impact_power
                per_dollor_generated_energy = (expected_KW_Output / project_funding_total)
                user_impact_for_watt = float(per_dollor_generated_energy) * float(user_financial_contribution)*1000
                user_impact += user_impact_for_watt
        return user_impact

    def user_impact_for_carbon_dioxide(self):
        all_payments = Payment.objects.payments(user=self).exclude(project__isnull=True)
        user_impact = 0
        POUNDS_CARBON_PER_KWH = 1.5
        for payment in all_payments:
            project = payment.project
            if project:
                user_financial_contribution = payment.amount
                project_funding_total = (int)(project.funding_goal)
                total_kwh_value = project.total_kwh_value
                carbon_dioxide_avoided_by_project = float(POUNDS_CARBON_PER_KWH) * float(total_kwh_value)
                per_dollor_avoided_co2 = carbon_dioxide_avoided_by_project/project_funding_total
                user_impact_for_carbon_dioxide = per_dollor_avoided_co2 * user_financial_contribution
                user_impact += user_impact_for_carbon_dioxide
        return user_impact

    def user_impact_of_acr_of_tree_save(self):
        all_payments = Payment.objects.payments(user=self).exclude(project__isnull=True)
        user_impact = 0
        ACRE_OF_TREES_PER_KWH = 0.0006
        for payment in all_payments:
            project = payment.project
            if project:
                user_financial_contribution = payment.amount
                project_funding_total = (int)(project.funding_goal)
                total_kwh_value = project.total_kwh_value
                acre_of_tree_save_by_project = float(ACRE_OF_TREES_PER_KWH) * float(total_kwh_value)
                per_dollor_saved_trees = acre_of_tree_save_by_project / project_funding_total
                # print ("aaaa", acr_of_tree_save_by_project,per_dollor_saved_trees)
                user_impact_for_saved_trees = per_dollor_saved_trees * user_financial_contribution
                user_impact += user_impact_for_saved_trees
        return user_impact

    def get_statistic_for_user(self, attr):
        """Calculates a user's individual impact by iterating through all the users payments, calculating
        what fraction of that project comprises of this user's donation, and calculates individual
        user impact using the statistics attribute (a KilowattStatsAggregator) and the fraction."""
        all_payments = Payment.objects.payments(user=self).exclude(project__isnull=True)
        user_impact = 0
        for payment in all_payments:
            project = payment.project
            if project:
                user_financial_contribution = payment.amount
                project_funding_total = (int)(project.funding_goal)
                project_impact = getattr(project.statistics, attr)
                user_impact_for_project = project_impact * user_financial_contribution * 1.0 / project_funding_total
                user_impact += user_impact_for_project
        return user_impact

    def get_full_name(self):
        '''
        Returns the first_name plus the last_name, with a space in between.
        '''
        full_name = '%s %s' % (self.user.first_name, self.user.last_name)
        if len(full_name.strip()) == 0:
            full_name = self.user.username
        return full_name.strip()