~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/browser/person.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2011-05-23 18:43:31 UTC
  • mfrom: (13084.2.6 page-match-rewrite-url)
  • Revision ID: launchpad@pqm.canonical.com-20110523184331-dhd2c7cgfuu49epw
[r=sinzui][bug=784273] Adds facility to the PageMatch to handle bad
        URIs

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
    'PersonEditLocationView',
29
29
    'PersonEditSSHKeysView',
30
30
    'PersonEditView',
 
31
    'PersonEditWikiNamesView',
31
32
    'PersonFacets',
32
33
    'PersonGPGView',
33
34
    'PersonIndexMenu',
99
100
from lazr.delegates import delegates
100
101
from lazr.restful.interface import copy_field
101
102
from lazr.restful.interfaces import IWebServiceClientRequest
102
 
from lazr.uri import URI
 
103
from lazr.uri import (
 
104
    InvalidURIError,
 
105
    URI,
 
106
    )
103
107
import pytz
104
108
from storm.expr import Join
105
109
from storm.zope.interfaces import IResultSet
161
165
    IGPGHandler,
162
166
    )
163
167
from canonical.launchpad.interfaces.launchpad import (
 
168
    ILaunchpadCelebrities,
164
169
    INotificationRecipientSet,
165
170
    UnknownRecipientError,
166
171
    )
167
172
from canonical.launchpad.interfaces.logintoken import ILoginTokenSet
 
173
from lp.services.messages.interfaces.message import (
 
174
    IDirectEmailAuthorization,
 
175
    QuotaReachedError,
 
176
    )
168
177
from canonical.launchpad.interfaces.oauth import IOAuthConsumerSet
 
178
from lp.registry.mail.notification import send_direct_contact_email
169
179
from canonical.launchpad.webapp import (
170
180
    ApplicationMenu,
171
181
    canonical_url,
195
205
from canonical.launchpad.webapp.publisher import LaunchpadView
196
206
from canonical.lazr.utils import smartquote
197
207
from lp.answers.browser.questiontarget import SearchQuestionsView
 
208
from lp.answers.interfaces.questioncollection import IQuestionSet
198
209
from lp.answers.enums import QuestionParticipation
199
 
from lp.answers.interfaces.questioncollection import IQuestionSet
200
210
from lp.answers.interfaces.questionsperson import IQuestionsPerson
201
211
from lp.app.browser.launchpadform import (
202
212
    action,
213
223
    NotFoundError,
214
224
    UnexpectedFormData,
215
225
    )
216
 
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
217
226
from lp.app.validators.email import valid_email
218
227
from lp.app.widgets.image import ImageChangeWidget
219
228
from lp.app.widgets.itemswidgets import (
224
233
    )
225
234
from lp.app.widgets.location import LocationWidget
226
235
from lp.app.widgets.password import PasswordChangeWidget
 
236
from lp.app.widgets.textwidgets import URIWidget
227
237
from lp.blueprints.browser.specificationtarget import HasSpecificationsView
228
238
from lp.blueprints.enums import SpecificationFilter
229
239
from lp.bugs.browser.bugtask import BugTaskSearchListingView
292
302
    ITeamMembershipSet,
293
303
    TeamMembershipStatus,
294
304
    )
295
 
from lp.registry.interfaces.wikiname import IWikiNameSet
296
 
from lp.registry.mail.notification import send_direct_contact_email
 
305
from lp.registry.interfaces.wikiname import (
 
306
    IWikiName,
 
307
    IWikiNameSet,
 
308
    )
297
309
from lp.registry.model.milestone import (
298
310
    Milestone,
299
311
    milestone_sort_key,
300
312
    )
301
313
from lp.services.fields import LocationField
302
314
from lp.services.geoip.interfaces import IRequestPreferredLanguages
303
 
from lp.services.messages.interfaces.message import (
304
 
    IDirectEmailAuthorization,
305
 
    QuotaReachedError,
306
 
    )
307
315
from lp.services.openid.adapters.openid import CurrentOpenIDEndPoint
308
316
from lp.services.openid.browser.openiddiscovery import (
309
317
    XRDSContentNegotiationMixin,
1046
1054
        'common_edithomepage',
1047
1055
        'editemailaddresses',
1048
1056
        'editlanguages',
 
1057
        'editwikinames',
1049
1058
        'editircnicknames',
1050
1059
        'editjabberids',
1051
1060
        'editsshkeys',
1095
1104
        return Link(target, text, icon='edit')
1096
1105
 
1097
1106
    @enabled_with_permission('launchpad.Edit')
 
1107
    def editwikinames(self):
 
1108
        target = '+editwikinames'
 
1109
        text = 'Update wiki names'
 
1110
        return Link(target, text, icon='edit')
 
1111
 
 
1112
    @enabled_with_permission('launchpad.Edit')
1098
1113
    def editircnicknames(self):
1099
1114
        target = '+editircnicknames'
1100
1115
        text = 'Update IRC nicknames'
2005
2020
 
2006
2021
        return package_links
2007
2022
 
2008
 
    @cachedproperty
2009
 
    def person_url(self):
2010
 
        return canonical_url(self.context)
2011
 
 
2012
2023
    def getBugSubscriberPackageSearchURL(self, distributionsourcepackage=None,
2013
2024
                                      advanced=False, extra_params=None):
2014
2025
        """Construct a default search URL for a distributionsourcepackage.
2033
2044
 
2034
2045
            params.update(extra_params)
2035
2046
 
 
2047
        person_url = canonical_url(self.context)
2036
2048
        query_string = urllib.urlencode(sorted(params.items()), doseq=True)
2037
2049
 
2038
2050
        if advanced:
2039
 
            return (self.person_url + '/+packagebugs-search?advanced=1&%s'
 
2051
            return (person_url + '/+packagebugs-search?advanced=1&%s'
2040
2052
                    % query_string)
2041
2053
        else:
2042
 
            return self.person_url + '/+packagebugs-search?%s' % query_string
 
2054
            return person_url + '/+packagebugs-search?%s' % query_string
2043
2055
 
2044
2056
    def getBugSubscriberPackageAdvancedSearchURL(self,
2045
2057
                                              distributionsourcepackage=None):
2777
2789
            check_permission('launchpad.Edit', self.context))
2778
2790
 
2779
2791
    @property
 
2792
    def should_show_wikinames_section(self):
 
2793
        """Should the 'Wiki names' section be shown?
 
2794
 
 
2795
        It's shown when the person has Wiki names registered or has rights
 
2796
        to register new ones.
 
2797
        """
 
2798
        return not self.context.wiki_names.is_empty() or (
 
2799
            check_permission('launchpad.Edit', self.context))
 
2800
 
 
2801
    @property
2780
2802
    def should_show_jabberids_section(self):
2781
2803
        """Should the 'Jabber IDs' section be shown?
2782
2804
 
3538
3560
                sCoC_util.modifySignature(sig_id, self.user, comment, False)
3539
3561
 
3540
3562
 
 
3563
class PersonEditWikiNamesView(LaunchpadFormView):
 
3564
    """View for ~person/+editwikinames"""
 
3565
 
 
3566
    schema = IWikiName
 
3567
    fields = ['wiki', 'wikiname']
 
3568
    # Use custom widgets solely to get the width correct.  The URIWidget has a
 
3569
    # CSS class that does not respect the displayWidth, thus the need to use a
 
3570
    # different cssClass.
 
3571
    custom_widget('wiki', URIWidget, displayWidth=40, cssClass="textType")
 
3572
    custom_widget('wikiname', TextWidget, displayWidth=40)
 
3573
 
 
3574
    @property
 
3575
    def label(self):
 
3576
        return smartquote("%s's wiki names" % self.context.displayname)
 
3577
 
 
3578
    @property
 
3579
    def next_url(self):
 
3580
        return canonical_url(self.context)
 
3581
 
 
3582
    cancel_url = next_url
 
3583
 
 
3584
    def setUpFields(self):
 
3585
        super(PersonEditWikiNamesView, self).setUpFields()
 
3586
        if self.context.wiki_names.count() > 0:
 
3587
            # Make the wiki and wiki_name entries optional on the edit page if
 
3588
            # one or more ids already exist, which allows the removal of ids
 
3589
            # without filling out the new wiki fields.
 
3590
            wiki_field = self.form_fields['wiki']
 
3591
            wikiname_field = self.form_fields['wikiname']
 
3592
            # Copy the fields so as not to modify the interface.
 
3593
            wiki_field.field = copy_field(wiki_field.field)
 
3594
            wiki_field.field.required = False
 
3595
            wikiname_field.field = copy_field(wikiname_field.field)
 
3596
            wikiname_field.field.required = False
 
3597
 
 
3598
    def _validateWikiURL(self, url):
 
3599
        """Validate the URL.
 
3600
 
 
3601
        Make sure that the result is a valid URL with only the
 
3602
        appropriate schemes.  The url is assumed to be a string.
 
3603
        """
 
3604
        if url is None:
 
3605
            return
 
3606
        try:
 
3607
            uri = URI(url)
 
3608
            if uri.scheme not in ('http', 'https'):
 
3609
                self.setFieldError(
 
3610
                    'wiki',
 
3611
                    structured(
 
3612
                        'The URL scheme "%(scheme)s" is not allowed.  '
 
3613
                        'Only http or https URLs may be used.',
 
3614
                        scheme=uri.scheme))
 
3615
        except InvalidURIError:
 
3616
            self.setFieldError(
 
3617
                'wiki',
 
3618
                structured(
 
3619
                    '"%(url)s" is not a valid URL.', url=url))
 
3620
 
 
3621
    def _validateWikiName(self, name):
 
3622
        """Ensure the wikiname is valid.
 
3623
 
 
3624
        It must not be longer than 100 characters.  Name is assumed to be a
 
3625
        string.
 
3626
        """
 
3627
        max_len = 100
 
3628
        if len(name) > max_len:
 
3629
            self.setFieldError(
 
3630
                'wikiname',
 
3631
                structured(
 
3632
                    'The wiki name cannot exceed %d characters.' % max_len))
 
3633
 
 
3634
    def _sanitizeWikiURL(self, url):
 
3635
        """Strip whitespaces and make sure :url ends in a single '/'."""
 
3636
        if not url:
 
3637
            return url
 
3638
        return '%s/' % url.strip().rstrip('/')
 
3639
 
 
3640
    def validate(self, data):
 
3641
        # If there are already form errors then just show them.
 
3642
        if self.errors:
 
3643
            return
 
3644
        wikiurl = self._sanitizeWikiURL(data.get('wiki'))
 
3645
        wikiname = data.get('wikiname')
 
3646
        if wikiurl or wikiname:
 
3647
            if not wikiurl:
 
3648
                self.setFieldError(
 
3649
                    'wiki',
 
3650
                    structured(
 
3651
                        'The Wiki URL must be specified.'))
 
3652
            if not wikiname:
 
3653
                self.setFieldError(
 
3654
                    'wikiname',
 
3655
                    structured(
 
3656
                        'The Wiki name must be specified.'))
 
3657
 
 
3658
        if self.errors:
 
3659
            return
 
3660
 
 
3661
        if wikiurl is not None:
 
3662
            self._validateWikiURL(wikiurl)
 
3663
        if wikiname is not None:
 
3664
            self._validateWikiName(wikiname)
 
3665
 
 
3666
        if self.errors:
 
3667
            return
 
3668
 
 
3669
        wikinameset = getUtility(IWikiNameSet)
 
3670
        existingwiki = wikinameset.getByWikiAndName(wikiurl, wikiname)
 
3671
        if existingwiki:
 
3672
            if existingwiki.person != self.context:
 
3673
                owner_name = urllib.quote(existingwiki.person.name)
 
3674
                merge_url = (
 
3675
                    '%s/+requestmerge?field.dupe_person=%s'
 
3676
                    % (canonical_url(getUtility(IPersonSet)), owner_name))
 
3677
                self.setFieldError(
 
3678
                    'wikiname',
 
3679
                    structured(
 
3680
                        'The WikiName %s%s is already registered by '
 
3681
                        '<a href="%s">%s</a>. If you think this is a '
 
3682
                        'duplicated account, you can <a href="%s">merge it'
 
3683
                        '</a> into your account.',
 
3684
                        wikiurl, wikiname,
 
3685
                        canonical_url(existingwiki.person),
 
3686
                        existingwiki.person.displayname, merge_url))
 
3687
            else:
 
3688
                # The person already has this wiki.
 
3689
                self.setFieldError(
 
3690
                    'wikiname',
 
3691
                    'The WikiName %s%s already belongs to you.' %
 
3692
                    (wikiurl, wikiname))
 
3693
 
 
3694
    def _save(self, wikiurl, wikiname):
 
3695
        """Given a wikiurl and wikiname, attempt to save it.
 
3696
 
 
3697
        Verify someone else doesn't have it already.
 
3698
        """
 
3699
 
 
3700
    @action(_("Save Changes"), name="save")
 
3701
    def save(self, action, data):
 
3702
        """Process the wiki names form."""
 
3703
        form = self.request.form
 
3704
        for obj in self.context.wiki_names:
 
3705
            if form.get('remove_%s' % obj.id):
 
3706
                obj.destroySelf()
 
3707
 
 
3708
        if not self.errors:
 
3709
            wikiurl = self._sanitizeWikiURL(data.get('wiki'))
 
3710
            wikiname = data.get('wikiname')
 
3711
            # If either url or name are present then they both must be
 
3712
            # entered.
 
3713
            if wikiurl and wikiname:
 
3714
                wikinameset = getUtility(IWikiNameSet)
 
3715
                wikinameset.new(self.context, wikiurl, wikiname)
 
3716
 
 
3717
 
3541
3718
class PersonEditIRCNicknamesView(LaunchpadFormView):
3542
3719
 
3543
3720
    schema = Interface