~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/vocabularies.py

[r=danilo][bug=878260] macro
        bugtarget-macros-search/simple-search-form: Show a title;
        drop the 'order by' widget; CSS changes.

Show diffs side-by-side

added added

removed removed

Lines of Context:
197
197
from lp.registry.model.sourcepackagename import SourcePackageName
198
198
from lp.registry.model.teammembership import TeamParticipation
199
199
from lp.services.database import bulk
 
200
from lp.services.features import getFeatureFlag
200
201
from lp.services.propertycache import (
201
202
    cachedproperty,
202
203
    get_property_cache,
210
211
 
211
212
    _table = Person
212
213
 
 
214
    def __init__(self, context=None):
 
215
        super(BasePersonVocabulary, self).__init__(context)
 
216
        self.enhanced_picker_enabled = bool(
 
217
            getFeatureFlag('disclosure.picker_enhancements.enabled'))
 
218
 
213
219
    def toTerm(self, obj):
214
220
        """Return the term for this object."""
215
221
        try:
298
304
            fti_query = quote(query)
299
305
            sql = "active = 't' AND (name LIKE %s OR fti @@ ftq(%s))" % (
300
306
                    like_query, fti_query)
301
 
            order_by = (
302
 
                '(CASE name WHEN %s THEN 1 '
303
 
                ' ELSE rank(fti, ftq(%s)) END) DESC, displayname, name'
304
 
                % (fti_query, fti_query))
 
307
            if getFeatureFlag('disclosure.picker_enhancements.enabled'):
 
308
                order_by = (
 
309
                    '(CASE name WHEN %s THEN 1 '
 
310
                    ' ELSE rank(fti, ftq(%s)) END) DESC, displayname, name'
 
311
                    % (fti_query, fti_query))
 
312
            else:
 
313
                order_by = self._orderBy
305
314
            return self._table.select(sql, orderBy=order_by, limit=100)
306
315
        return self.emptySelectResults()
307
316
 
577
586
 
578
587
    def _doSearch(self, text="", vocab_filter=None):
579
588
        """Return the people/teams whose fti or email address match :text:"""
 
589
        if self.enhanced_picker_enabled:
 
590
            return self._doSearchWithImprovedSorting(text, vocab_filter)
 
591
        else:
 
592
            return self._doSearchWithOriginalSorting(text, vocab_filter)
 
593
 
 
594
    def _doSearchWithOriginalSorting(self, text="", vocab_filter=None):
 
595
        private_query, private_tables = self._privateTeamQueryAndTables()
 
596
        exact_match = None
 
597
        extra_clauses = [self.extra_clause]
 
598
        if vocab_filter:
 
599
            extra_clauses.extend(vocab_filter.filter_terms)
 
600
 
 
601
        # Short circuit if there is no search text - all valid people and
 
602
        # teams have been requested. We still honour the vocab filter.
 
603
        if not text:
 
604
            tables = [
 
605
                Person,
 
606
                Join(self.cache_table_name,
 
607
                     SQL("%s.id = Person.id" % self.cache_table_name)),
 
608
                ]
 
609
            tables.extend(private_tables)
 
610
            result = self.store.using(*tables).find(
 
611
                Person,
 
612
                And(
 
613
                    Or(Person.visibility == PersonVisibility.PUBLIC,
 
614
                       private_query,
 
615
                       ),
 
616
                    Person.merged == None,
 
617
                    *extra_clauses
 
618
                    )
 
619
                )
 
620
        else:
 
621
            # Do a full search based on the text given.
 
622
 
 
623
            # The queries are broken up into several steps for efficiency.
 
624
            # The public person and team searches do not need to join with the
 
625
            # TeamParticipation table, which is very expensive.  The search
 
626
            # for private teams does need that table but the number of private
 
627
            # teams is very small so the cost is not great.
 
628
 
 
629
            # First search for public persons and teams that match the text.
 
630
            public_tables = [
 
631
                Person,
 
632
                LeftJoin(EmailAddress, EmailAddress.person == Person.id),
 
633
                ]
 
634
 
 
635
            # Create an inner query that will match public persons and teams
 
636
            # that have the search text in the fti, at the start of the email
 
637
            # address, or as their full IRC nickname.
 
638
            # Since we may be eliminating results with the limit to improve
 
639
            # performance, we sort by the rank, so that we will always get
 
640
            # the best results. The fti rank will be between 0 and 1.
 
641
            # Note we use lower() instead of the non-standard ILIKE because
 
642
            # ILIKE doesn't hit the indexes.
 
643
            # The '%%' is necessary because storm variable substitution
 
644
            # converts it to '%'.
 
645
            public_inner_textual_select = SQL("""
 
646
                SELECT id FROM (
 
647
                    SELECT Person.id, 100 AS rank
 
648
                    FROM Person
 
649
                    WHERE name = ?
 
650
                    UNION ALL
 
651
                    SELECT Person.id, rank(fti, ftq(?))
 
652
                    FROM Person
 
653
                    WHERE Person.fti @@ ftq(?)
 
654
                    UNION ALL
 
655
                    SELECT Person.id, 10 AS rank
 
656
                    FROM Person, IrcId
 
657
                    WHERE IrcId.person = Person.id
 
658
                        AND lower(IrcId.nickname) = ?
 
659
                    UNION ALL
 
660
                    SELECT Person.id, 1 AS rank
 
661
                    FROM Person, EmailAddress
 
662
                    WHERE EmailAddress.person = Person.id
 
663
                        AND lower(email) LIKE ? || '%%'
 
664
                        AND EmailAddress.status IN (?, ?)
 
665
                    ) AS public_subquery
 
666
                ORDER BY rank DESC
 
667
                LIMIT ?
 
668
                """, (text, text, text, text, text,
 
669
                      EmailAddressStatus.VALIDATED.value,
 
670
                      EmailAddressStatus.PREFERRED.value,
 
671
                      self.LIMIT))
 
672
 
 
673
            public_result = self.store.using(*public_tables).find(
 
674
                Person,
 
675
                And(
 
676
                    Person.id.is_in(public_inner_textual_select),
 
677
                    Person.visibility == PersonVisibility.PUBLIC,
 
678
                    Person.merged == None,
 
679
                    Or(  # A valid person-or-team is either a team...
 
680
                       # Note: 'Not' due to Bug 244768.
 
681
                       Not(Person.teamowner == None),
 
682
                       # Or a person who has a preferred email address.
 
683
                       EmailAddress.status == EmailAddressStatus.PREFERRED),
 
684
                    ))
 
685
            # The public query doesn't need to be ordered as it will be done
 
686
            # at the end.
 
687
            public_result.order_by()
 
688
 
 
689
            # Next search for the private teams.
 
690
            private_query, private_tables = self._privateTeamQueryAndTables()
 
691
            private_tables = [Person] + private_tables
 
692
 
 
693
            # Searching for private teams that match can be easier since we
 
694
            # are only interested in teams.  Teams can have email addresses
 
695
            # but we're electing to ignore them here.
 
696
            private_result = self.store.using(*private_tables).find(
 
697
                Person,
 
698
                And(
 
699
                    SQL('Person.fti @@ ftq(?)', [text]),
 
700
                    private_query,
 
701
                    )
 
702
                )
 
703
 
 
704
            private_result.order_by(SQL('rank(fti, ftq(?)) DESC', [text]))
 
705
            private_result.config(limit=self.LIMIT)
 
706
 
 
707
            combined_result = public_result.union(private_result)
 
708
            # Eliminate default ordering.
 
709
            combined_result.order_by()
 
710
            # XXX: BradCrittenden 2009-04-26 bug=217644: The use of Alias and
 
711
            # _get_select() is a work-around for .count() not working
 
712
            # with the 'distinct' option.
 
713
            subselect = Alias(combined_result._get_select(), 'Person')
 
714
            exact_match = (Person.name == text)
 
715
            result = self.store.using(subselect).find(
 
716
                (Person, exact_match),
 
717
                *extra_clauses)
 
718
        # XXX: BradCrittenden 2009-05-07 bug=373228: A bug in Storm prevents
 
719
        # setting the 'distinct' and 'limit' options in a single call to
 
720
        # .config().  The work-around is to split them up.  Note the limit has
 
721
        # to be after the call to 'order_by' for this work-around to be
 
722
        # effective.
 
723
        result.config(distinct=True)
 
724
        if exact_match is not None:
 
725
            # A DISTINCT requires that the sort parameters appear in the
 
726
            # select, but it will break the vocabulary if it returns a list of
 
727
            # tuples instead of a list of Person objects, so we create
 
728
            # another subselect to sort after the DISTINCT is done.
 
729
            distinct_subselect = Alias(result._get_select(), 'Person')
 
730
            result = self.store.using(distinct_subselect).find(Person)
 
731
            result.order_by(
 
732
                Desc(exact_match), Person.displayname, Person.name)
 
733
        else:
 
734
            result.order_by(Person.displayname, Person.name)
 
735
        result.config(limit=self.LIMIT)
 
736
        return result
 
737
 
 
738
    def _doSearchWithImprovedSorting(self, text="", vocab_filter=None):
 
739
        """Return the people/teams whose fti or email address match :text:"""
580
740
 
581
741
        private_query, private_tables = self._privateTeamQueryAndTables()
582
742
        extra_clauses = [self.extra_clause]
729
889
                    *extra_clauses),
730
890
                )
731
891
            # Better ranked matches go first.
732
 
            if self._karma_context_constraint:
 
892
            if (getFeatureFlag('disclosure.person_affiliation_rank.enabled')
 
893
                and self._karma_context_constraint):
733
894
                rank_order = SQL("""
734
895
                    rank * COALESCE(
735
896
                        (SELECT LOG(karmavalue) FROM KarmaCache
819
980
                        self.extra_clause)
820
981
            result = self.store.using(*tables).find(Person, query)
821
982
        else:
822
 
            name_match_query = SQL("""
823
 
                Person.name LIKE ? || '%%'
824
 
                OR lower(Person.displayname) LIKE ? || '%%'
825
 
                OR Person.fti @@ ftq(?)
826
 
                """, [text, text, text]),
 
983
            if self.enhanced_picker_enabled:
 
984
                name_match_query = SQL("""
 
985
                    Person.name LIKE ? || '%%'
 
986
                    OR lower(Person.displayname) LIKE ? || '%%'
 
987
                    OR Person.fti @@ ftq(?)
 
988
                    """, [text, text, text]),
 
989
            else:
 
990
                name_match_query = SQL("Person.fti @@ ftq(%s)" % quote(text))
827
991
 
828
992
            email_storm_query = self.store.find(
829
993
                EmailAddress.personID,
1885
2049
                    obj.__class__.__name__, obj.id)
1886
2050
            obj = obj.pillar
1887
2051
 
1888
 
        title = '%s' % obj.title
 
2052
        enhanced = bool(getFeatureFlag(
 
2053
            'disclosure.target_picker_enhancements.enabled'))
 
2054
        if enhanced:
 
2055
            title = '%s' % obj.title
 
2056
        else:
 
2057
            title = '%s (%s)' % (obj.title, obj.pillar_category)
1889
2058
        return SimpleTerm(obj, obj.name, title)
1890
2059
 
1891
2060
    def getTermByToken(self, token):