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")
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")
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)
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)
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)
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')
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
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)
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)
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
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
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()