Merge lp:~jtv/launchpad/bug-423705 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jtv/launchpad/bug-423705
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~jtv/launchpad/bug-423705
Reviewer Review Type Date Requested Status
Michael Nelson (community) ui Approve
Abel Deuring (community) Approve
Review via email: mp+11519@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

= Bug 423705: 3.0 Person Translations page =

This branch converts the Person page in Translations to the 3.0 style.
It also transforms the translation activity (previously "history")
listing on that page into something more like what we drew up in Buenos
Aires (minus the squiggles where the straight lines were supposed to be
:-)

The activity listing no longer floods the page with your complete
translation history; the full list has moved to a new "activity" page.
The person page now only shows the latest 10 entries.

What you see on the new page is a bit more detailed than the short list,
but also less detailed than what the existing Person page shows. The
reason is that it originally had a mixed bag of purposes, including
helping people reach the translations they've been working on. But for
that we now have the "translations you need to review" and "translations
you can help complete" listings.

I tried to get a two-column layout working for this page, but couldn't
because the elements on the page are much too variable. Each of the
portlets is conditional, all with different conditions. No static
layout is going to make that work well for even a reasonable number of
cases.

== Test ==

Best to run all Translations stories, as I did, but in particular:
{{{
./bin/test -vv -t xx-person-activity
}}}

== Demo and Q/A ==

For a nicely filled example, go to Carlos' home page:

    https://translations.launchpad.dev/~carlos

It gets better if you look at the same page but logged in as Carlos
(<email address hidden> / test).

From there, click on the "See all" link on the right, at the bottom of
the activity list. That takes you to the new, batched full activity
listing.

Also look at an empty page:

    https://translations.launchpad.dev/~salgado

And finally, there's the warning if you haven't set your preferred
languages. Log in as Brad (<email address hidden> / test) and visit Brad's
Translations page:

    https://translations.launchpad.dev/~bac

The "Please set them now" link will take you to the settings page where
you can select preferred languages.

== Lint ==
{{{
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/pagetitles.py
  lib/canonical/launchpad/webapp/configure.zcml
  lib/canonical/launchpad/webapp/tales.py
  lib/lp/translations/browser/configure.zcml
  lib/lp/translations/browser/person.py
  lib/lp/translations/stories/standalone/xx-person-activity.txt
  lib/lp/translations/templates/person-navlinks.pt
  lib/lp/translations/templates/person-translation-activity.pt
  lib/lp/translations/templates/person-translations-to-complete-table.pt
  lib/lp/translations/templates/person-translations-to-review-table.pt
  lib/lp/translations/templates/person-translations.pt

=== Pylint notices ===

lib/canonical/launchpad/webapp/tales.py
    21: [F0401] Unable to import 'lazr.enum' (No module named enum)
}}}

I'm not planning to do anything about that last one.

Jeroen

Revision history for this message
Abel Deuring (adeuring) wrote :

Hi Jeroen,

a very nice branch. We discussed two minor changes in IRC, please apply them

review: Approve
Revision history for this message
Michael Nelson (michael.nelson) wrote :

Great work Jeroen!

The index page looks great!

Note: RE the icons not displaying, I'd say you just need to add icon='info' to your menu 'Link's.

We agreed it would be good to perhaps not include the edit translation guidelines here on the index page, but are not including it as part of this update.

<jtv> Anyone free to give me a UI review for https://code.edge.launchpad.net/~jtv/launchpad/bug-423705/+merge/11519 ? noodles775 maybe?
<noodles775> jtv: I could - but I just saw that barry will be around on ocr soon - would you mind if I leave it to him?
<jtv> noodles775: I _hate_ to nag (well, except end-users) but this branch has been sitting on the review queue for a while, been accidentally removed from it, put back etc. If barry has no time, that pushes this to next week.
* jtv bats eyelids at noodles
<noodles775> jtv: ah, do you need to leave soon?
<jtv> noodles775: ideally, yes, though I don't think it's particularly likely :-)
<noodles775> OK, looking now...
<jtv> noodles775: great, thanks!
<noodles775> jtv: just noticed that I think you're doing something similar to https://launchpad.dev/~carlos/+related-software
<noodles775> with the 'sub-menu'...
<noodles775> The only difference being the icons?
<noodles775> jtv: btw, the index page looks great!
<jtv> noodles775: actually I copied the whole idea from there. :) _Keine_ _blasse_ _Ahnung_ why those icons don't appear for me. :(
<noodles775> heh
<jtv> noodles775: (glad you like the page btw)
<noodles775> jtv: I'm not sure about having the full translation groups table there? Well, at least the edit translation guidelines link... what do you think? I just noticed if you edit the translation guideline you obviously don't end up back on the same page...
<noodles775> But then again, I guess that's similar to clicking on the needs [link]2 strings reviewed[/link]
<jtv> noodles775: it's one of those things I would've liked to update, but avoided on the "timely and within the review limit beats perfect" principle.
<noodles775> jtv: yep, sure.
<jtv> noodles775: I do agree it can do with a makeover though
<jtv> (Or since this is all just outward appearance, is it really a combover?)

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/pagetitles.py'
--- lib/canonical/launchpad/pagetitles.py 2009-09-10 01:35:03 +0000
+++ lib/canonical/launchpad/pagetitles.py 2009-09-10 10:09:18 +0000
@@ -739,8 +739,6 @@
739739
740person_specworkload = ContextDisplayName('Blueprint workload for %s')740person_specworkload = ContextDisplayName('Blueprint workload for %s')
741741
742person_translations = ContextDisplayName('Translations related to %s')
743
744person_translations_to_review = ContextDisplayName(742person_translations_to_review = ContextDisplayName(
745 'Translations for review by %s')743 'Translations for review by %s')
746744
747745
=== modified file 'lib/canonical/launchpad/webapp/configure.zcml'
--- lib/canonical/launchpad/webapp/configure.zcml 2009-09-01 08:37:31 +0000
+++ lib/canonical/launchpad/webapp/configure.zcml 2009-09-09 11:45:55 +0000
@@ -513,6 +513,12 @@
513 name="fmt"513 name="fmt"
514 />514 />
515 <adapter515 <adapter
516 for="lp.translations.interfaces.pofile.IPOFile"
517 provides="zope.traversing.interfaces.IPathAdapter"
518 factory="canonical.launchpad.webapp.tales.POFileFormatterAPI"
519 name="fmt"
520 />
521 <adapter
516 for="*"522 for="*"
517 provides="zope.traversing.interfaces.IPathAdapter"523 provides="zope.traversing.interfaces.IPathAdapter"
518 factory="canonical.launchpad.webapp.tales.PermissionRequiredQuery"524 factory="canonical.launchpad.webapp.tales.PermissionRequiredQuery"
519525
=== modified file 'lib/canonical/launchpad/webapp/tales.py'
--- lib/canonical/launchpad/webapp/tales.py 2009-09-07 12:16:29 +0000
+++ lib/canonical/launchpad/webapp/tales.py 2009-09-09 11:45:55 +0000
@@ -3181,3 +3181,27 @@
3181 def displayname(self, view_name, rootsite=None):3181 def displayname(self, view_name, rootsite=None):
3182 """Return the displayname as a string."""3182 """Return the displayname as a string."""
3183 return self._context.title3183 return self._context.title
3184
3185
3186class POFileFormatterAPI(ObjectFormatterAPI):
3187 """Adapter for `IPOFile` objects to a formatted string."""
3188
3189 traversable_names = {
3190 'link': 'link',
3191 'url': 'url',
3192 'displayname': 'displayname',
3193 }
3194
3195 def url(self, view_name=None, rootsite='translations'):
3196 """See `ObjectFormatterAPI`."""
3197 return super(POFileFormatterAPI, self).url(view_name, rootsite)
3198
3199 def link(self, view_name, rootsite='translations'):
3200 """See `ObjectFormatterAPI`."""
3201 pofile = self._context
3202 url = self.url(view_name, rootsite)
3203 return u'<a href="%s">%s</a>' % (url, cgi.escape(pofile.title))
3204
3205 def displayname(self, view_name, rootsite=None):
3206 """Return the displayname as a string."""
3207 return self._context.title
31843208
=== modified file 'lib/lp/translations/browser/configure.zcml'
--- lib/lp/translations/browser/configure.zcml 2009-09-01 11:09:53 +0000
+++ lib/lp/translations/browser/configure.zcml 2009-09-10 10:09:18 +0000
@@ -709,7 +709,6 @@
709 <browser:page709 <browser:page
710 for="lp.registry.interfaces.person.IPerson"710 for="lp.registry.interfaces.person.IPerson"
711 name="+licensing"711 name="+licensing"
712 facet="translations"
713 class="712 class="
714 lp.translations.browser.person.PersonTranslationRelicensingView"713 lp.translations.browser.person.PersonTranslationRelicensingView"
715 permission="launchpad.Edit"714 permission="launchpad.Edit"
@@ -719,12 +718,24 @@
719 for="lp.registry.interfaces.person.IPerson"718 for="lp.registry.interfaces.person.IPerson"
720 name="+imports"719 name="+imports"
721 class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"720 class="lp.translations.browser.hastranslationimports.HasTranslationImportsView"
722 facet="translations"
723 permission="zope.Public"721 permission="zope.Public"
724 template="../templates/hastranslationimports-index.pt"722 template="../templates/hastranslationimports-index.pt"
725 layer="canonical.launchpad.layers.TranslationsLayer"/>723 layer="canonical.launchpad.layers.TranslationsLayer"/>
726 <browser:page724 <browser:page
727 for="lp.registry.interfaces.person.IPerson"725 for="lp.registry.interfaces.person.IPerson"
726 class="lp.translations.browser.person.PersonTranslationView"
727 permission="zope.Public"
728 name="+person-navlinks"
729 template="../templates/person-navlinks.pt"/>
730 <browser:page
731 for="lp.registry.interfaces.person.IPerson"
732 name="+activity"
733 class="lp.translations.browser.person.TranslationActivityView"
734 permission="zope.Public"
735 template="../templates/person-translation-activity.pt"
736 layer="canonical.launchpad.layers.TranslationsLayer"/>
737 <browser:page
738 for="lp.registry.interfaces.person.IPerson"
728 name="+translations-to-review"739 name="+translations-to-review"
729 class="lp.translations.browser.person.PersonTranslationView"740 class="lp.translations.browser.person.PersonTranslationView"
730 permission="zope.Public"741 permission="zope.Public"
731742
=== modified file 'lib/lp/translations/browser/person.py'
--- lib/lp/translations/browser/person.py 2009-09-09 16:49:35 +0000
+++ lib/lp/translations/browser/person.py 2009-09-10 11:56:21 +0000
@@ -8,13 +8,16 @@
8__all__ = [8__all__ = [
9 'PersonTranslationView',9 'PersonTranslationView',
10 'PersonTranslationRelicensingView',10 'PersonTranslationRelicensingView',
11 'TranslationActivityView',
11]12]
1213
13from datetime import datetime, timedelta14from datetime import datetime, timedelta
14import pytz15import pytz
15import urllib16import urllib
17
16from zope.app.form.browser import TextWidget18from zope.app.form.browser import TextWidget
17from zope.component import getUtility19from zope.component import getUtility
20from zope.interface import implements, Interface
1821
19from canonical.launchpad import _22from canonical.launchpad import _
20from canonical.launchpad.webapp.interfaces import ILaunchBag23from canonical.launchpad.webapp.interfaces import ILaunchBag
@@ -25,7 +28,6 @@
25from canonical.launchpad.webapp.batching import BatchNavigator28from canonical.launchpad.webapp.batching import BatchNavigator
26from canonical.launchpad.webapp.publisher import LaunchpadView29from canonical.launchpad.webapp.publisher import LaunchpadView
27from canonical.widgets import LaunchpadRadioWidget30from canonical.widgets import LaunchpadRadioWidget
28from lp.registry.interfaces.person import IPerson
29from lp.registry.interfaces.sourcepackage import ISourcePackage31from lp.registry.interfaces.sourcepackage import ISourcePackage
30from lp.translations.browser.translationlinksaggregator import (32from lp.translations.browser.translationlinksaggregator import (
31 TranslationLinksAggregator)33 TranslationLinksAggregator)
@@ -89,18 +91,56 @@
89 return pofile.untranslatedCount()91 return pofile.untranslatedCount()
9092
9193
94def compose_pofile_filter_url(pofile, person):
95 """Compose URL for `Person`'s contributions to `POFile`."""
96 person_name = urllib.urlencode({'person': person.name})
97 return canonical_url(pofile) + "/+filter?%s" % person_name
98
99
100class ActivityDescriptor:
101 """Description of a past translation activity."""
102
103 date = None
104 title = None
105 url = None
106
107 def __init__(self, person, pofiletranslator):
108 """Describe a past translation activity by `person`.
109
110 :param person: The `Person` whose activity is being described.
111 :param pofiletranslator: A `POFileTranslator` record for
112 `person`.
113 """
114 assert person == pofiletranslator.person, (
115 "This POFileTranslator record is for the wrong person.")
116
117 self.date = pofiletranslator.date_last_touched
118
119 pofile = pofiletranslator.pofile
120
121 self.title = pofile.potemplate.translationtarget.title
122 self.url = compose_pofile_filter_url(pofile, person)
123
124
92def person_is_reviewer(person):125def person_is_reviewer(person):
93 """Is `person` a translations reviewer?"""126 """Is `person` a translations reviewer?"""
94 groups = ITranslationsPerson(person).translation_groups127 groups = ITranslationsPerson(person).translation_groups
95 return groups.any() is not None128 return groups.any() is not None
96129
97130
131class IPersonTranslationsMenu(Interface):
132 """Marker interface for `Person` Translations navigation menu."""
133
134
98class PersonTranslationsMenu(NavigationMenu):135class PersonTranslationsMenu(NavigationMenu):
99136
100 usedfor = IPerson137 usedfor = IPersonTranslationsMenu
101 facet = 'translations'138 facet = 'translations'
102 links = ('overview', 'licensing', 'imports', 'translations_to_review')139 links = ('overview', 'licensing', 'imports', 'translations_to_review')
103 title = "Related pages"140
141 @property
142 def person(self):
143 return self.context.context
104144
105 def overview(self):145 def overview(self):
106 text = 'Overview'146 text = 'Overview'
@@ -112,37 +152,45 @@
112152
113 def licensing(self):153 def licensing(self):
114 text = 'Translations licensing'154 text = 'Translations licensing'
115 enabled = (self.context == self.user)155 enabled = (self.person == self.user)
116 return Link('+licensing', text, enabled=enabled)156 return Link('+licensing', text, enabled=enabled)
117157
118 def translations_to_review(self):158 def translations_to_review(self):
119 text = 'Translations to review'159 text = 'Translations to review'
120 enabled = person_is_reviewer(self.context)160 enabled = person_is_reviewer(self.person)
121 return Link('+translations-to-review', text, enabled=enabled)161 return Link('+translations-to-review', text, enabled=enabled)
122162
123163
124class PersonTranslationView(LaunchpadView):164class PersonTranslationView(LaunchpadView):
125 """View for translation-related Person pages."""165 """View for translation-related Person pages."""
166 implements(IPersonTranslationsMenu)
126167
127 _pofiletranslator_cache = None168 reviews_to_show = 10
128169
129 def __init__(self, *args, **kwargs):170 def __init__(self, *args, **kwargs):
130 super(PersonTranslationView, self).__init__(*args, **kwargs)171 super(PersonTranslationView, self).__init__(*args, **kwargs)
131 now = datetime.now(pytz.timezone('UTC'))172 now = datetime.now(pytz.timezone('UTC'))
132 self.history_horizon = now - timedelta(90, 0, 0)173 self.history_horizon = now - timedelta(90, 0, 0)
133174
134 @cachedproperty175 @property
135 def batchnav(self):176 def page_title(self):
177 return "Translations related to %s" % self.context.displayname
178
179 @cachedproperty
180 def recent_activity(self):
181 """Recent translation activity by this person."""
182 entries = ITranslationsPerson(self.context).translation_history[:10]
183 return [ActivityDescriptor(self.context, entry) for entry in entries]
184
185 @cachedproperty
186 def latest_activity(self):
187 """Single latest translation activity by this person."""
136 translations_person = ITranslationsPerson(self.context)188 translations_person = ITranslationsPerson(self.context)
137 batchnav = BatchNavigator(189 latest = list(translations_person.translation_history[:1])
138 translations_person.translation_history, self.request)190 if len(latest) == 0:
139191 return None
140 pofiletranslatorset = getUtility(IPOFileTranslatorSet)192 else:
141 batch = batchnav.currentBatch()193 return ActivityDescriptor(self.context, latest[0])
142 self._pofiletranslator_cache = (
143 pofiletranslatorset.prefetchPOFileTranslatorRelations(batch))
144
145 return batchnav
146194
147 @cachedproperty195 @cachedproperty
148 def translation_groups(self):196 def translation_groups(self):
@@ -156,11 +204,6 @@
156 translations_person = ITranslationsPerson(self.context)204 translations_person = ITranslationsPerson(self.context)
157 return list(translations_person.translators)205 return list(translations_person.translators)
158206
159 @cachedproperty
160 def person_filter_querystring(self):
161 """Return person's name appropriate for including in links."""
162 return urllib.urlencode({'person': self.context.name})
163
164 @property207 @property
165 def person_is_reviewer(self):208 def person_is_reviewer(self):
166 """Is this person in a translation group?"""209 """Is this person in a translation group?"""
@@ -429,3 +472,29 @@
429 "Unknown allow_relicensing value: %r" % allow_relicensing)472 "Unknown allow_relicensing value: %r" % allow_relicensing)
430 self.next_url = self.getSafeRedirectURL(data['back_to'])473 self.next_url = self.getSafeRedirectURL(data['back_to'])
431474
475
476class TranslationActivityView(LaunchpadView):
477 """View for person's activity listing."""
478
479 _pofiletranslator_cache = None
480
481 @property
482 def page_title(self):
483 return "Translation activity by %s" % self.context.displayname
484
485 @cachedproperty
486 def batchnav(self):
487 """Iterate over person's translation_history."""
488 translations_person = ITranslationsPerson(self.context)
489 batchnav = BatchNavigator(
490 translations_person.translation_history, self.request)
491
492 pofiletranslatorset = getUtility(IPOFileTranslatorSet)
493 batch = batchnav.currentBatch()
494 self._pofiletranslator_cache = (
495 pofiletranslatorset.prefetchPOFileTranslatorRelations(batch))
496
497 return batchnav
498
499 def composeURL(self, pofile):
500 return compose_pofile_filter_url(pofile, self.context)
432501
=== renamed file 'lib/lp/translations/stories/standalone/xx-person-translations.txt' => 'lib/lp/translations/stories/standalone/xx-person-activity.txt'
--- lib/lp/translations/stories/standalone/xx-person-translations.txt 2009-09-01 09:51:12 +0000
+++ lib/lp/translations/stories/standalone/xx-person-activity.txt 2009-09-10 11:56:21 +0000
@@ -1,13 +1,13 @@
1Test that the person's translation history page displays the latest1Person's translation activity
2translations and translation groups related to the person in question.2=============================
3
4A person's translation activity page displays the translations that the
5person has contributed to.
6
7Here the person whose activity we're going to look at is Carlos.
38
4 >>> anon_browser.open(9 >>> anon_browser.open(
5 ... "http://translations.launchpad.dev/people/carlos/+translations")10 ... "http://translations.launchpad.dev/people/carlos/+activity")
6
7 >>> header = find_tag_by_id(
8 ... anon_browser.contents, 'translation-history-header')
9 >>> print extract_text(header)
10 Applications and packages translated
1111
12The user can see in the page navigation that Carlos has so far worked12The user can see in the page navigation that Carlos has so far worked
13on six files, five of which are shown on this page.13on six files, five of which are shown on this page.
@@ -20,74 +20,60 @@
2020
21 # Prints a heading and formatted list of POFiles and latest submissions.21 # Prints a heading and formatted list of POFiles and latest submissions.
22 >>> import re22 >>> import re
23 >>> def print_list_of_contributions(listing):23 >>> def print_activity_list(listing):
24 ... listing_head = listing.findAll('thead')[0].findAll('tr')[0]24 ... for row in listing.findAll('tr'):
25 ... heading_cells = listing_head.findAll('th')25 ... print extract_text(row)
26 ... print "%-24s %-8s %-8s %-26s" % (26
27 ... extract_text(heading_cells[0]),27 >>> listing = find_tag_by_id(anon_browser.contents, 'activity-table')
28 ... extract_text(heading_cells[1])[:6] + '...',28 >>> print_activity_list(listing)
29 ... extract_text(heading_cells[2])[:6] + '...',29 2007-07-12
30 ... extract_text(heading_cells[3]))30 English (en) translation of pkgconf-mozilla in Ubuntu Hoary package
31 ... listing_body = listing.find('tbody')31 "mozilla"
32 ... for row in listing_body.findAll('tr'):32 2007-04-07
33 ... cells = row.findAll('td')33 Spanish (es) translation of alsa-utils in alsa-utils trunk
34 ... pofile = extract_text(cells[0])34 2007-01-24
35 ... if len(pofile) > 24:35 Spanish (es) translation of man in Ubuntu Hoary package "evolution"
36 ... pofile = pofile[:21] + '...'36 2006-12-22
37 ... untranslated = extract_text(cells[1])37 Spanish (es) translation of evolution-2.2 in Evolution trunk
38 ... needs_review = extract_text(cells[2])38 2005-10-11
39 ... message = re.sub(' +', ' ', extract_text(cells[3]))39 Japanese (ja) translation of evolution-2.2 in Ubuntu Hoary package
40 ... if len(message) > 26:40 "evolution"
41 ... message = message[:23] + '...'41
42 ... print "%-24s %-10s %-10s %-26s" % (42Clicking one of the entries takes you to a page listing all of Carlos'
43 ... pofile, untranslated, needs_review, message)43contributions to the selected translation.
44
45 >>> listing = find_tags_by_class(anon_browser.contents, 'listing')[0]
46 >>> print_list_of_contributions(listing)
47 Translation file Untra... Need... Latest translation
48 English (en) translat... 6 0 2007-07-12 auto, esddsp...
49 Spanish (es) translat... 3 0 2007-04-07 _: EMAIL OF ...
50 Spanish (es) translat... 0 0 2007-01-24 test man pag...
51 Spanish (es) translat... 14 2 2006-12-22 have lalalala
52 Japanese (ja) transla... 21 0 2005-10-11 %d contact %...
53
54When the user clicks one of the entries, he is shown a page listing
55all translations done by Carlos in this file.
5644
57 >>> alsa_utils_link = 'Spanish (es) translation of alsa-utils in alsa-utils trunk'45 >>> alsa_utils_link = 'Spanish (es) translation of alsa-utils in alsa-utils trunk'
58 >>> anon_browser.getLink(alsa_utils_link).click()46 >>> anon_browser.getLink(alsa_utils_link).click()
59 >>> anon_browser.title47 >>> print anon_browser.title
60 'Translations by Carlos...of alsa-utils...'48 Translations by Carlos...of alsa-utils...
6149
62From the full listing of all contributions, Carlos can jump to the pages50
63containing only untranslated or only translations that need reviewing.51URL-escaped user names
6452----------------------
65 >>> anon_browser.goBack()53
66 >>> untranslated_link = find_tags_by_class(anon_browser.contents,54Since the user's name is included in the URL, and user names can contain
67 ... 'untranslated-count')[1]55some slightly weird characters, it is escaped especially for this usage.
68 >>> extract_link_from_tag(untranslated_link)56
69 u'/alsa-utils/trunk/+pots/alsa-utils/es/+translate?show=untranslated'57For instance, here's a user called a+b.
70
71 >>> unreviewed_link = find_tags_by_class(anon_browser.contents,
72 ... 'unreviewed-count')[1]
73 >>> extract_link_from_tag(unreviewed_link)
74 u'/alsa-utils/trunk/+pots/alsa-utils/es/+translate?show=new_suggestions'
75
76A person is created with a name containing characters that need escaping
77before being used in query strings.
7858
79 >>> login('carlos@canonical.com')59 >>> login('carlos@canonical.com')
80 >>> foobar = factory.makePerson(name='foo+bar', email='blah@test.com')60 >>> ab = factory.makePerson(name='a+b')
81 >>> message = factory.makeTranslationMessage(translator=foobar)61 >>> message = factory.makeTranslationMessage(translator=ab)
82 >>> person_url = canonical_url(foobar, rootsite='translations')62 >>> person_url = canonical_url(ab, rootsite='translations')
83 >>> logout()63 >>> logout()
8464
85When that person goes to see the translations she has done, she sees65When a+b goes to see the translations she has done, she sees a correctly
86a correctly encoded link to a filtered PO file page.66encoded link to a filtered PO file page.
8767
88 >>> user_browser.open(person_url)68 >>> user_browser.open(person_url + '/+activity')
89 >>> table = first_tag_by_class(user_browser.contents, 'listing')69 >>> table = find_tag_by_id(user_browser.contents, 'activity-table')
90 >>> tbody = table.find('tbody')70 >>> link = table.find('a')
91 >>> link = tbody.find('a')71 >>> url = link['href']
92 >>> link['href'].split('/')[-1]72 >>> print url.split('/')[-1]
93 u'+filter?person=foo%2Bbar'73 +filter?person=a%2Bb
74
75Because of this, the link actually works.
76
77 >>> user_browser.open(url.encode('ascii'))
78 >>> print user_browser.title
79 Translations by ...
9480
=== added file 'lib/lp/translations/templates/person-navlinks.pt'
--- lib/lp/translations/templates/person-navlinks.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/person-navlinks.pt 2009-09-10 11:56:21 +0000
@@ -0,0 +1,21 @@
1<tal:root
2 xmlns:tal="http://xml.zope.org/namespaces/tal"
3 xmlns:metal="http://xml.zope.org/namespaces/metal"
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">
6
7 <ul id="navlinks" class="horizontal">
8 <li>
9 <a tal:replace="structure view/menu:navigation/overview/render"/>
10 </li>
11 <li>
12 <a tal:replace="structure view/menu:navigation/licensing/render"/>
13 </li>
14 <li>
15 <a tal:replace="structure view/menu:navigation/imports/render"/>
16 </li>
17 <li>
18 <a tal:replace="structure view/menu:navigation/translations_to_review/render"/>
19 </li>
20 </ul>
21</tal:root>
022
=== added file 'lib/lp/translations/templates/person-translation-activity.pt'
--- lib/lp/translations/templates/person-translation-activity.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/translations/templates/person-translation-activity.pt 2009-09-10 11:53:09 +0000
@@ -0,0 +1,41 @@
1<html
2 xmlns="http://www.w3.org/1999/xhtml"
3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_only"
7>
8<body>
9 <div metal:fill-slot="main">
10 <div class="top-portlet" style="max-width: 800px">
11 <p>
12 <tal:person replace="structure context/fmt:link">R. Viewer</tal:person>
13 has contributed to the following translations.
14 </p>
15
16 <div class="lesser" id="top-navigation">
17 <tal:navigation content="structure view/batchnav/@@+navigation-links-upper" />
18 </div>
19
20 <table class="listing" id="activity-table">
21 <tr tal:repeat="record view/batchnav/currentBatch">
22 <td tal:attributes="title record/date_last_touched/fmt:datetime"
23 tal:content="record/date_last_touched/fmt:approximatedate">
24 2009-10-28
25 </td>
26 <td>
27 <a tal:attributes="href python:view.composeURL(record.pofile)"
28 tal:content="record/pofile/title">
29 Greek (el) translations of alsa-utils in alsa-utils trunk
30 </a>
31 </td>
32 </tr>
33 </table>
34
35 <div class="lesser">
36 <tal:navigation content="structure view/batchnav/@@+navigation-links-lower" />
37 </div>
38 </div>
39 </div>
40</body>
41</html>
042
=== modified file 'lib/lp/translations/templates/person-translations-to-complete-table.pt'
--- lib/lp/translations/templates/person-translations-to-complete-table.pt 2009-08-28 09:44:31 +0000
+++ lib/lp/translations/templates/person-translations-to-complete-table.pt 2009-09-09 09:51:14 +0000
@@ -4,7 +4,7 @@
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">5 omit-tag="">
66
7<table class="listing" id="translations-to-complete-table">7<table class="summary" id="translations-to-complete-table">
8 <tr tal:repeat="target_info view/top_projects_and_packages_to_translate">8 <tr tal:repeat="target_info view/top_projects_and_packages_to_translate">
9 <td>9 <td>
10 <tal:product condition="target_info/is_product"10 <tal:product condition="target_info/is_product"
1111
=== modified file 'lib/lp/translations/templates/person-translations-to-review-table.pt'
--- lib/lp/translations/templates/person-translations-to-review-table.pt 2009-08-12 07:59:37 +0000
+++ lib/lp/translations/templates/person-translations-to-review-table.pt 2009-09-09 09:51:14 +0000
@@ -4,7 +4,7 @@
4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"4 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
5 omit-tag="">5 omit-tag="">
66
7<table class="listing" id="translations-to-review-table">7<table class="summary" id="translations-to-review-table">
8 <tr tal:repeat="target_info view/top_projects_and_packages_to_review">8 <tr tal:repeat="target_info view/top_projects_and_packages_to_review">
9 <td>9 <td>
10 <tal:product condition="target_info/is_product"10 <tal:product condition="target_info/is_product"
1111
=== modified file 'lib/lp/translations/templates/person-translations.pt'
--- lib/lp/translations/templates/person-translations.pt 2009-09-09 16:49:35 +0000
+++ lib/lp/translations/templates/person-translations.pt 2009-09-10 11:53:09 +0000
@@ -3,35 +3,56 @@
3 xmlns:tal="http://xml.zope.org/namespaces/tal"3 xmlns:tal="http://xml.zope.org/namespaces/tal"
4 xmlns:metal="http://xml.zope.org/namespaces/metal"4 xmlns:metal="http://xml.zope.org/namespaces/metal"
5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"5 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
6 metal:use-macro="view/macro:page/main_only"
6 i18n:domain="launchpad"7 i18n:domain="launchpad"
7 xml:lang="en"
8 lang="en"
9 dir="ltr"
10 metal:use-macro="view/macro:page/onecolumn"
11>8>
129
13<body>10<body>
1411
15<h1>Translations related to <tal:name content="context/displayname" /></h1>12<div metal:fill-slot="heading">
13 <h1 tal:content="view/page_title" />
14</div>
1615
17<div metal:fill-slot="main">16<div metal:fill-slot="main">
17 <div class="top-portlet">
18 <tal:navlinks replace="structure context/@@+person-navlinks" />
19 </div>
1820
19 <tal:me condition="view/person_includes_me">21 <tal:me condition="view/person_includes_me">
20 <p tal:condition="view/requires_preferred_languages" id="no-languages">22 <div tal:condition="view/requires_preferred_languages"
23 class="portlet"
24 id="no-languages">
21 You have not selected your preferred languages. Please25 You have not selected your preferred languages. Please
22 <a href="/+editmylanguages" id="set-languages">set them now</a>.26 <a href="/+editmylanguages" id="set-languages">set them now</a>.
23 </p>27 </div>
28 </tal:me>
29
30 <div class="portlet">
31 <tal:active tal:condition="view/latest_activity">
32 Last translation activity by
33 <tal:name replace="context/displayname">Foo Bar</tal:name>
34 was
35 <tal:date replace="view/latest_activity/date/fmt:displaydate">
36 on 2009-10-12</tal:date>.
37 </tal:active>
38 <tal:inactive tal:condition="not: view/latest_activity">
39 No translation activities recorded for
40 <tal:name replace="context/displayname">Foo Bar</tal:name>.
41 </tal:inactive>
42 </div>
43
44 <tal:me condition="view/person_includes_me">
24 <tal:reviewer condition="view/person_is_reviewer">45 <tal:reviewer condition="view/person_is_reviewer">
25 <div id="translations-to-review-section" style="max-width:800px">46 <div id="translations-to-review-section" class="portlet">
26 <span style="float:right">
27 <a href="http://help.launchpad.net/Translations/Reviewing">
28 Why is this important?
29 </a>
30 </span>
31 <h2>Translations you need to review</h2>47 <h2>Translations you need to review</h2>
48 <span style="display:block; text-align:right">
49 <a href="http://help.launchpad.net/Translations/Reviewing">
50 Why is this important?
51 </a>
52 </span>
32 <metal:translations-to-review53 <metal:translations-to-review
33 tal:replace="structure context/@@+translations-to-review-table" />54 tal:replace="structure context/@@+translations-to-review-table" />
34 <span style="float:right"55 <span style="display:block; text-align:right"
35 tal:condition="python: view.num_projects_and_packages_to_review > 1">56 tal:condition="python: view.num_projects_and_packages_to_review > 1">
36 <a href="+translations-to-review" id="translations-to-review-link">57 <a href="+translations-to-review" id="translations-to-review-link">
37 See all58 See all
@@ -42,9 +63,11 @@
42 </span>63 </span>
43 </div>64 </div>
44 </tal:reviewer>65 </tal:reviewer>
66 </tal:me>
4567
68 <tal:me condition="view/person_includes_me">
46 <tal:translator condition="view/person_is_translator">69 <tal:translator condition="view/person_is_translator">
47 <div id="translations-to-complete-section" style="max-width:800px">70 <div id="translations-to-complete-section" class="portlet">
48 <h2>Translations you can help complete</h2>71 <h2>Translations you can help complete</h2>
49 <metal:translations-to-complete72 <metal:translations-to-complete
50 tal:replace="structure context/@@+translations-to-complete-table" />73 tal:replace="structure context/@@+translations-to-complete-table" />
@@ -52,87 +75,40 @@
52 </tal:translator>75 </tal:translator>
53 </tal:me>76 </tal:me>
5477
5578 <tal:block tal:define="activities view/recent_activity">
56 <h2 id="translation-history-header">Applications and packages translated</h2>79 <tal:block condition="activities">
5780 <div class="portlet">
58 <tal:block define="history view/batchnav/currentBatch">81 <h2>Activity</h2>
5982 <p>
60 <tal:block condition="history">83 These are the translations that
6184 <tal:name replace="context/displayname">Foo Bar</tal:name>
62 <div class="lesser" id="top-navigation">85 last worked on:
63 <tal:navigation content="structure view/batchnav/@@+navigation-links-upper" />86 </p>
64 </div>87 <table class="summary">
6588 <tr tal:repeat="activity activities">
66 <table class="listing" style="max-width:800px">89 <td tal:attributes="title activity/date/fmt:datetime"
67 <thead>90 tal:content="activity/date/fmt:approximatedate">
68 <tr>91 2009-08-15
69 <th>Translation file</th>92 </td>
70 <th>Untranslated</th>93 <td>
71 <th>Need review</th>94 <a tal:content="activity/title"
72 <th>Latest translation</th>95 tal:attributes="href activity/url">foo bar in package bla</a>
73 </tr>96 </td>
74 </thead>97 </tr>
75 <tbody>98 </table>
76 <tr tal:repeat="record history">99 <span style="display:block; text-align:right">
77 <tal:block define="translationmessage record/latest_message">100 <a href="+activity">See all</a>
78 <td><a tal:content="record/pofile/title"101 </span>
79 tal:attributes="href string:${record/pofile/fmt:url}/+filter?${view/person_filter_querystring}">foo bar in package bla</a>102 </div>
80 </td>
81 <td><a tal:content="record/pofile/untranslatedCount"
82 tal:attributes="href string:${record/pofile/fmt:url}/+translate?show=untranslated"
83 class="untranslated-count"
84 >17</a>
85 </td>
86 <td><a tal:content="record/pofile/unreviewedCount"
87 tal:attributes="href string:${record/pofile/fmt:url}/+translate?show=new_suggestions"
88 class="unreviewed-count"
89 >17</a>
90 </td>
91 <td style="padding-left: 2em;">
92 <em
93 tal:attributes="title record/date_last_touched/fmt:datetime"
94 tal:content="record/date_last_touched/fmt:approximatedate"
95 style="margin-left: -2em;" />
96 <div tal:content="translationmessage/potmsgset/singular_text" />
97 <div
98 tal:condition="python:not(view.should_display_message(translationmessage))">
99 <strong>For privacy reasons this translation is not visible to
100 anonymous visitors. To see it,
101 <a href="+login">log in</a> first.</strong></div>
102 <div
103 tal:condition="python:view.should_display_message(translationmessage)">
104 <tal:block
105 tal:condition="translationmessage/msgstr0"
106 tal:content="translationmessage/msgstr0/translation" />
107 </div>
108 </td>
109 </tal:block>
110 </tr>
111 </tbody>
112 </table>
113
114 <div class="lesser">
115 <tal:navigation content="structure view/batchnav/@@+navigation-links-lower" />
116 </div>
117
118 </tal:block>
119
120 <tal:block condition="not: history">
121 <p><i>No translations recorded from
122 <span tal:replace="context/displayname">Foo Bar</span>.</i></p>
123 </tal:block>103 </tal:block>
124 </tal:block>104 </tal:block>
125105
126 <tal:block condition="view/translation_groups">106 <div tal:condition="view/translation_groups" class="portlet">
127
128 <h2>Translation Groups</h2>107 <h2>Translation Groups</h2>
129
130 <span tal:replace="context/displayname">Mark Shuttleworth</span> is108 <span tal:replace="context/displayname">Mark Shuttleworth</span> is
131 a member of the following translation groups:109 a member of the following translation groups:
132110
133 <table class="listing"111 <table class="listing" id="translation-group-memberships">
134 id="translation-group-memberships"
135 style="max-width:800px">
136 <thead>112 <thead>
137 <tr>113 <tr>
138 <th>Translation group</th>114 <th>Translation group</th>
@@ -158,9 +134,7 @@
158 </tr>134 </tr>
159 </tbody>135 </tbody>
160 </table>136 </table>
161137 </div>
162 </tal:block>
163
164</div>138</div>
165</body>139</body>
166</html>140</html>