class Citation(models.Model): portfolio_entry = models.ForeignKey(PortfolioEntry) # [0] url = models.URLField(null=True, verbose_name="URL") contributor_role = models.CharField(max_length=200, null=True) languages = models.CharField(max_length=200, null=True) first_commit_time = models.DateTimeField(null=True) date_created = models.DateTimeField(default=datetime.datetime.utcnow) is_published = models.BooleanField(default=False) # unpublished == Unread is_deleted = models.BooleanField(default=False) ignored_due_to_duplicate = models.BooleanField(default=False) old_summary = models.TextField(null=True, default=None) objects = models.Manager() untrashed = UntrashedCitationManager() @property def summary(self): # FIXME: Pluralize correctly. # FIXME: Use "since year_started" if self.url is not None: return url2printably_short(self.url, CUTOFF=38) elif self.languages is not None: return "Coded in %s." % (self.languages, ) def get_languages_as_list(self): if self.languages is None: return [] return [ lang.strip() for lang in self.languages.split(",") if lang.strip() ] def get_url_or_guess(self): if self.url: return self.url def save_and_check_for_duplicates(self): # FIXME: Cache summaries in the DB so this query is faster. duplicates = [ citation for citation in Citation.objects.filter( portfolio_entry=self.portfolio_entry) if (citation.pk != self.pk) and (citation.summary == self.summary) ] if duplicates: self.ignored_due_to_duplicate = True return self.save() # [0]: FIXME: Let's learn how to use Django's ManyToManyField etc. def __unicode__(self): if self.pk is not None: pk = self.pk else: pk = 'unassigned' return "pk=%s, summary=%s" % (pk, self.summary)
class Person(models.Model): """ A human bean. """ # {{{ homepage_url = models.URLField(default="", blank=True) user = models.ForeignKey(User, unique=True) gotten_name_from_ohloh = models.BooleanField(default=False) last_polled = models.DateTimeField(default=datetime.datetime(1970, 1, 1)) show_email = models.BooleanField(default=False) bio = models.TextField(blank=True) contact_blurb = models.TextField(blank=True) expand_next_steps = models.BooleanField(default=True) photo = models.ImageField( upload_to=lambda a, b: 'static/photos/profile-photos/' + generate_person_photo_path(a, b), default='') photo_thumbnail = models.ImageField( upload_to=lambda a, b: 'static/photos/profile-photos/' + generate_person_photo_path(a, b, suffix="-thumbnail"), default='', null=True) photo_thumbnail_30px_wide = models.ImageField( upload_to=lambda a, b: 'static/photos/profile-photos/' + generate_person_photo_path(a, b, suffix="-thumbnail-30px-wide"), default='', null=True) photo_thumbnail_20px_wide = models.ImageField( upload_to=lambda a, b: 'static/photos/profile-photos/' + generate_person_photo_path(a, b, suffix="-thumbnail-20px-wide"), default='', null=True) blacklisted_repository_committers = models.ManyToManyField( RepositoryCommitter) dont_guess_my_location = models.BooleanField(default=False) location_confirmed = models.BooleanField(default=False) location_display_name = models.CharField(max_length=255, blank=True, default=DEFAULT_LOCATION, verbose_name='Location') latitude = models.FloatField(null=False, default=-37.3049962) longitude = models.FloatField(null=False, default=-12.6790445) email_me_re_projects = models.BooleanField( default=True, verbose_name='Email me periodically about activity in my projects') irc_nick = models.CharField(max_length=30, blank=True, null=True) @staticmethod def create_dummy(first_name="", email=None, **kwargs): # Generate a random string to use as a username. Keep it short # so that it doesn't overflow the username field! username = uuid.uuid4().hex[:16] if email is None: email = "*****@*****.**" % username user = User(username=username, first_name=first_name, email=email) data = {'user': user} # If the caller of create_dummy passes in a user, then we won't use the # user defined above data.update(kwargs) # Save the user after the update, so we don't save a new user if one # was never needed user = data['user'] user.save() person = user.get_profile() for key, value in data.items(): setattr(person, key, value) person.save() return person def location_is_public(self): # If you change this method, change the method immediately below this # one (Person.inaccessible_islanders) return self.location_confirmed and self.location_display_name @staticmethod def inaccessible_islanders(): # If you change this method, change the method immediately above this # one (location_is_public) return Person.objects.filter( Q(location_confirmed=False) | Q(location_display_name='')) def get_public_location_or_default(self): if self.location_is_public(): return self.location_display_name else: return DEFAULT_LOCATION def get_public_latitude_or_default(self): if self.location_is_public(): return self.latitude else: return DEFAULT_LATITUDE def get_public_longitude_or_default(self): if self.location_is_public(): return self.longitude else: return DEFAULT_LONGITUDE def __unicode__(self): return "username: %s, name: %s %s" % ( self.user.username, self.user.first_name, self.user.last_name) def get_photo_url_or_default(self): try: return self.photo.url except (IOError, ValueError): return '/static/images/profile-photos/penguin.png' @staticmethod def get_from_session_key(session_key): '''Based almost entirely on http://www.djangosnippets.org/snippets/1276/ Thanks jdunck!''' session_engine = __import__(settings.SESSION_ENGINE, {}, {}, ['']) session_wrapper = session_engine.SessionStore(session_key) user_id = session_wrapper.get(SESSION_KEY) auth_backend = load_backend(session_wrapper.get(BACKEND_SESSION_KEY)) if user_id and auth_backend: return Person.objects.get(user=auth_backend.get_user(user_id)) else: return None def get_photo_thumbnail_url_or_default(self): try: return self.photo_thumbnail.url except (IOError, ValueError): return '/static/images/profile-photos/penguin-40px.png' def get_photo_thumbnail_width(self): try: return self.photo_thumbnail.width except (IOError, ValueError): return 40 def get_photo_thumbnail_height(self): try: return self.photo_thumbnail.height except (IOError, ValueError): return 51 def get_photo_thumbnail_30px_wide_url_or_default(self): try: return self.photo_thumbnail_30px_wide.url except (IOError, ValueError): return '/static/images/profile-photos/penguin-30px.png' def get_photo_thumbnail_20px_wide_url_or_default(self): try: return self.photo_thumbnail_20px_wide.url except (IOError, ValueError): return '/static/images/profile-photos/penguin-20px.png' def get_photo_thumbnail_width_20px(self): try: return self.photo_thumbnail_20px_wide.width except (IOError, ValueError): return 20 def get_photo_thumbnail_height_20px(self): try: return self.photo_thumbnail_20px_wide.height except (IOError, ValueError): return 20 def get_published_portfolio_entries(self): return PortfolioEntry.published_ones.filter(person=self) def get_nonarchived_published_portfolio_entries(self): return PortfolioEntry.published_ones.filter(person=self, is_archived=False) def get_maintainer_portfolio_entries(self): """ Return the PortfolioEntries for which this person wants to receive maintainer updates. """ return PortfolioEntry.published_ones.filter( person=self, receive_maintainer_updates=True) def get_list_of_all_published_projects(self): # This method looks familiar but testing -- jl return self.get_published_portfolio_entries() def get_list_of_all_project_names(self): # if you change this method, be sure to increment the version number in # the cache key above return list(self.get_published_portfolio_entries().values_list( 'project__name', flat=True).distinct()) def get_display_names_of_nonarchived_projects(self): return list( self.get_nonarchived_published_portfolio_entries().values_list( 'project__display_name', flat=True).distinct()) @staticmethod def only_terms_with_results(terms): # Remove terms whose hit counts are zero. terms_with_results = [] for term in terms: query = mysite.search.view_helpers.Query(terms=[term]) hit_count = query.get_or_create_cached_hit_count() if hit_count != 0: terms_with_results.append(term) return terms_with_results def get_recommended_search_terms(self): if settings.RECOMMEND_BUGS: return self._get_recommended_search_terms() return [] def _get_recommended_search_terms(self): # {{{ terms = [] # Add terms based on languages in citations citations = self.get_published_citations_flat() for c in citations: terms.extend(c.get_languages_as_list()) # Add terms based on projects in citations terms.extend([ pfe.project.name for pfe in self.get_published_portfolio_entries() if pfe.project.name and pfe.project.name.strip() ]) # Add terms based on tags terms.extend([tag.text for tag in self.get_tags_for_recommendations()]) # Remove duplicates terms = sorted(set(terms), key=lambda s: s.lower()) return Person.only_terms_with_results(terms) # FIXME: Add support for recommended projects. # FIXME: Add support for recommended project tags. # }}} def get_published_citations_flat(self): return sum([ list(pfe.get_published_citations()) for pfe in self.get_published_portfolio_entries() ], []) def get_tag_texts_for_map(self): """Return a list of Tags linked to this Person. Tags that would be useful from the map view of the people list""" my_tag_texts = Tag.objects.filter(link_person_tag__person=self).extra( select={'lowername': 'LOWER(text)'}) without_irrelevant_tags = my_tag_texts.exclude( tag_type__name__in=['understands_not', 'studying']) just_distinct_lowername = without_irrelevant_tags.values( 'lowername').distinct().order_by('lowername') text_and_lower = just_distinct_lowername.values_list( 'lowername', 'text') lower_set_so_far = set() ret = [] for (lower, text) in text_and_lower: if lower in lower_set_so_far: continue ret.append(text) lower_set_so_far.add(lower) return ret def get_tags_as_dict(self): ret = collections.defaultdict(set) for link in self.link_person_tag_set.all(): ret[link.tag.tag_type.name].add(link.tag.text.lower()) return ret def get_tag_descriptions_for_keyword(self, keyword): keyword = keyword.lower() d = self.get_tags_as_dict() return sorted([ TagType.short_name2long_name[short] for short in [key for key in d if (keyword in d[key])] ]) def get_tags_for_recommendations(self): """Return a list of Tags linked to this Person. For use with bug recommendations.""" exclude_me = TagType.objects.filter(name='understands_not') return [ link.tag for link in self.link_person_tag_set.all() if link.tag.tag_type not in exclude_me ] def get_full_name(self): # {{{ name = self.user.first_name if self.user.first_name and self.user.last_name: name += " " name += self.user.last_name return name # }}} def get_full_name_with_nbsps(self): full_name = self.get_full_name() full_name_escaped = cgi.escape(full_name) full_name_escaped_with_nbsps = re.sub("\s+", " ", full_name_escaped) return full_name_escaped_with_nbsps def get_full_name_or_username(self): return self.get_full_name() or self.user.username def get_full_name_and_username(self): full_name_start = ("%s (" % self.get_full_name() if self.user.first_name or self.user.last_name else "") full_name_end = (")" if self.user.first_name or self.user.last_name else "") return "%s%s%s" % (full_name_start, self.user.username, full_name_end) def generate_thumbnail_from_photo(self): if self.photo: width = 40 self.photo.file.seek(0) scaled_down = get_image_data_scaled(self.photo.file.read(), width) self.photo_thumbnail.save('', ContentFile(scaled_down)) width = 30 self.photo.file.seek(0) scaled_down = get_image_data_scaled(self.photo.file.read(), width) self.photo_thumbnail_30px_wide.save('', ContentFile(scaled_down)) width = 20 self.photo.file.seek(0) scaled_down = get_image_data_scaled(self.photo.file.read(), width) self.photo_thumbnail_20px_wide.save('', ContentFile(scaled_down)) def get_collaborators_for_landing_page(self, n=9): projects = set( [e.project for e in self.get_published_portfolio_entries()]) infinity = 10000 collaborator_lists = [] for project in projects: people = project.get_n_other_contributors_than(n=infinity, person=self) people = random.sample(people, min(n, len(people))) collaborator_lists.append(people) round_robin = mysite.profile.view_helpers.roundrobin( *collaborator_lists) collaborators = set() while len(collaborators) < n: try: collaborators.add(round_robin.next()) except StopIteration: break collaborators = list(collaborators) # don't forget, this has a side effect and returns None random.shuffle(collaborators) return collaborators @property def profile_url(self): return reverse( mysite.profile.views.display_person_web, kwargs={'user_to_display__username': self.user.username}) @staticmethod def get_by_username(username): return Person.objects.get(user__username=username) def should_be_nudged_about_location(self): return not self.location_confirmed and not self.dont_guess_my_location def get_coolness_factor(self, unhashed_tiebreaker): '''This function's output is used as the sort order in (at least) the periodic emails. You can be more cool if you: * Have projects * Have a picture * Have user tags set * Are a wannahelper of something and finally we break ties by get_full_name_or_username(), just so that we have a predictable sort. .''' hashed_tiebreaker = hashlib.sha1(unhashed_tiebreaker).hexdigest() factor = (bool(self.get_list_of_all_project_names()), bool(self.get_tags_as_dict()), bool(self.photo), bool(self.projects_i_wanna_help), hashed_tiebreaker) return factor def generate_new_unsubscribe_token(self): token = UnsubscribeToken(string=uuid.uuid4().hex, owner=self) token.save() return token
class Citation(models.Model): portfolio_entry = models.ForeignKey(PortfolioEntry) # [0] url = models.URLField(null=True, verbose_name="URL") contributor_role = models.CharField(max_length=200, null=True) data_import_attempt = models.ForeignKey(DataImportAttempt, null=True) languages = models.CharField(max_length=200, null=True) first_commit_time = models.DateTimeField(null=True) date_created = models.DateTimeField(default=datetime.datetime.utcnow) is_published = models.BooleanField(default=False) # unpublished == Unread is_deleted = models.BooleanField(default=False) ignored_due_to_duplicate = models.BooleanField(default=False) old_summary = models.TextField(null=True, default=None) objects = models.Manager() untrashed = UntrashedCitationManager() @property def summary(self): # FIXME: Pluralize correctly. # FIXME: Use "since year_started" if self.data_import_attempt: if self.data_import_attempt.source == 'db' and ( self.contributor_role == 'Maintainer'): return 'Maintain a package in Debian.' if self.data_import_attempt.source == 'db' and ( self.contributor_role.startswith('Maintainer of')): return self.contributor_role if self.data_import_attempt.source in ('gh', 'ga'): return '%s a repository on Github.' % self.contributor_role if self.data_import_attempt.source in ['rs', 'ou']: if not self.languages: return "Committed to codebase (%s)" % ( self.data_import_attempt.get_source_display(), ) return "Coded in %s (%s)" % ( self.languages, self.data_import_attempt.get_source_display(), ) elif self.data_import_attempt.source == 'lp': return "Participated in %s (%s)" % ( self.contributor_role, self.data_import_attempt.get_source_display()) elif self.data_import_attempt.source == 'bb': return "Created a repository on Bitbucket." else: raise ValueError, "There's a DIA of a kind I don't know how to summarize." elif self.url is not None: return url2printably_short(self.url, CUTOFF=38) elif self.languages is not None: return "Coded in %s." % (self.languages, ) raise ValueError( "There's no DIA and I don't know how to summarize this.") def get_languages_as_list(self): if self.languages is None: return [] return [ lang.strip() for lang in self.languages.split(",") if lang.strip() ] def get_url_or_guess(self): if self.url: return self.url else: if self.data_import_attempt: if self.data_import_attempt.source in ['rs', 'ou']: try: project_name = unicode( self.portfolio_entry.project.name, 'utf-8') return "http://www.ohloh.net/search?%s" % http.urlencode( {u'q': project_name}) except: logger.warning( "During Citation.get_url_or_guess, we failed to encode the project name correctly." ) # This is just a temporary workaround since the above # line was causing errors. return "http://www.ohloh.net/search?q=%s" % self.portfolio_entry.project.name elif self.data_import_attempt.source == 'lp': return "https://launchpad.net/~%s" % urllib.quote( self.data_import_attempt.query) def save_and_check_for_duplicates(self): # FIXME: Cache summaries in the DB so this query is faster. duplicates = [ citation for citation in Citation.objects.filter( portfolio_entry=self.portfolio_entry) if (citation.pk != self.pk) and (citation.summary == self.summary) ] if duplicates: self.ignored_due_to_duplicate = True return self.save() # [0]: FIXME: Let's learn how to use Django's ManyToManyField etc. def __unicode__(self): if self.pk is not None: pk = self.pk else: pk = 'unassigned' return "pk=%s, summary=%s" % (pk, self.summary)