← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/rdf-links-0 into lp:launchpad/devel

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/rdf-links-0 into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)


This is my branch to reduce the work performed in +rdf views to prevent
timeouts.

    lp:~sinzui/launchpad/rdf-links-0
    Diff size: 550
    Launchpad bug:
          https://bugs.launchpad.net/bugs/360699
          https://bugs.launchpad.net/bugs/30793
    Test command: ./bin/test -vv \
          -t stories/.*rdf
    Pre-implementation: lifeless
    Target release: 10.10


Reduce the work performed in +rdf views to prevent timeouts
-----------------------------------------------------------

The example project RDF that times out are those owned by large teams. The
template uses a macro that renders the personal data for each member. Though
the method is optimised for a small team, it sill never scale to a large
team. Instead of using the macro to expand the owner, use a reference to the
person/team's rdf entry.

This problem is larger than to reported oopses. Project pages are indexed by
bots following links. The RDF for the owning teams also times out if you
try to access it; the link in in the head, not body of the page.

This branch also introduces a base class for RDF views because the
implementations are identical except for template and filename.


Rules
-----

    * Update the RDF templates to include a reference to owner instead
      embedding personal data.
    * Remove the method that tries to collect team member information since
      it will not be used

    ADDENDUM
    * Extract a base class for the RDF views. Each subclass must provide
      a template and a filename.


QA
--

    * Visit https://edge.launchpad.net/bughelper-data/+rdf
    * Verify the page does not timeout
    * Visit https://edge.launchpad.net/~bugsquad/+rdf
    * Verify the page does not timeout


Lint
----

Linting changed files:
  lib/lp/registry/browser/__init__.py
  lib/lp/registry/browser/person.py
  lib/lp/registry/browser/product.py
  lib/lp/registry/browser/productrelease.py
  lib/lp/registry/browser/productseries.py
  lib/lp/registry/browser/project.py
  lib/lp/registry/stories/person/xx-person-rdf.txt
  lib/lp/registry/stories/product/xx-product-rdf.txt
  lib/lp/registry/stories/project/xx-project-rdf.txt
  lib/lp/registry/templates/person-rdf-contents.pt
  lib/lp/registry/templates/product-rdf.pt
  lib/lp/registry/templates/productrelease-rdf.pt
  lib/lp/registry/templates/project-rdf.pt

^ Lint reports a lot in indentation and line length issues un the rdf tests.
I will fix these after the review because the fix will make it appear that
I changed the entire file.


Test
----

Updated the tests to verify that members are referenced as resources instead
of being embedded.

    * lib/lp/registry/stories/product/xx-product-rdf.txt
    * lib/lp/registry/stories/project/xx-project-rdf.txt
    * lib/lp/registry/stories/person/xx-person-rdf.txt
      * Updated the ascii test to use carlos directly since he is not embedded
        in the team RDF.
      * Removed the embedded member in team test for mugshots. The image
        tests for users continue to work.


Implementation
--------------

Updated the templates to reference the person/team instead of embedding
the person/member's information. Remove trailing whitespace from some
templates and removed the definition of the macro used to expand members.

    * lib/lp/registry/templates/product-rdf.pt
    * lib/lp/registry/templates/productrelease-rdf.pt
    * lib/lp/registry/templates/project-rdf.pt
    * lib/lp/registry/templates/person-rdf-contents.pt

Extracted BaseRdfView view and updated the views to inherit. The existing
tests verify the filename and content types.

    * lib/lp/registry/browser/__init__.py
    * lib/lp/registry/browser/product.py
    * lib/lp/registry/browser/productrelease.py
    * lib/lp/registry/browser/productseries.py
    * lib/lp/registry/browser/project.py
    * lib/lp/registry/browser/person.py
      * Also removed buildMemberData() which is not needed.
-- 
https://code.launchpad.net/~sinzui/launchpad/rdf-links-0/+merge/36475
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/rdf-links-0 into lp:launchpad/devel.
=== modified file 'lib/lp/registry/browser/__init__.py'
--- lib/lp/registry/browser/__init__.py	2010-09-21 04:21:16 +0000
+++ lib/lp/registry/browser/__init__.py	2010-09-23 17:41:09 +0000
@@ -6,6 +6,7 @@
 __metaclass__ = type
 
 __all__ = [
+    'BaseRdfView',
     'get_status_counts',
     'MilestoneOverlayMixin',
     'RegistryEditFormView',
@@ -256,3 +257,30 @@
     @action("Change", name='change')
     def change_action(self, action, data):
         self.updateContextFromData(data)
+
+
+class BaseRdfView:
+    """A view that sets its mime-type to application/rdf+xml."""
+
+    template = None
+    filename = None
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self):
+        """Render RDF output, and return it as a string encoded in UTF-8.
+
+        Render the page template to produce RDF output.
+        The return value is string data encoded in UTF-8.
+
+        As a side-effect, HTTP headers are set for the mime type
+        and filename for download."""
+        self.request.response.setHeader('Content-Type', 'application/rdf+xml')
+        self.request.response.setHeader(
+            'Content-Disposition', 'attachment; filename=%s.rdf' % (
+             self.filename))
+        unicodedata = self.template()
+        encodeddata = unicodedata.encode('utf-8')
+        return encodeddata

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2010-09-21 16:06:38 +0000
+++ lib/lp/registry/browser/person.py	2010-09-23 17:41:09 +0000
@@ -242,6 +242,7 @@
 from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
 from lp.code.errors import InvalidNamespace
 from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
+from lp.registry.browser import BaseRdfView
 from lp.registry.browser.branding import BrandingChangeView
 from lp.registry.browser.mailinglists import enabled_with_active_mailing_list
 from lp.registry.browser.menu import (
@@ -1611,28 +1612,15 @@
         self.preferredemail = email
 
 
-class PersonRdfView:
+class PersonRdfView(BaseRdfView):
     """A view that embeds PersonRdfContentsView in a standalone page."""
 
     template = ViewPageTemplateFile(
         '../templates/person-rdf.pt')
 
-    def __call__(self):
-        """Render RDF output, and return it as a string encoded in UTF-8.
-
-        Render the page template to produce RDF output.
-        The return value is string data encoded in UTF-8.
-
-        As a side-effect, HTTP headers are set for the mime type
-        and filename for download."""
-        self.request.response.setHeader('content-type',
-                                        'application/rdf+xml')
-        self.request.response.setHeader('Content-Disposition',
-                                        'attachment; filename=%s.rdf' %
-                                            self.context.name)
-        unicodedata = self.template()
-        encodeddata = unicodedata.encode('utf-8')
-        return encodeddata
+    @property
+    def filename(self):
+        return self.context.name
 
 
 class PersonRdfContentsView:
@@ -1649,30 +1637,6 @@
         self.context = context
         self.request = request
 
-    def buildMemberData(self):
-        members = []
-        members_by_id = {}
-        raw_members = list(self.context.allmembers)
-        if not raw_members:
-            # Empty teams have nothing to offer.
-            return []
-        personset = getUtility(IPersonSet)
-        personset.cacheBrandingForPeople(raw_members)
-        for member in raw_members:
-            decorated_member = PersonWithKeysAndPreferredEmail(member)
-            members.append(decorated_member)
-            members_by_id[member.id] = decorated_member
-        sshkeyset = getUtility(ISSHKeySet)
-        gpgkeyset = getUtility(IGPGKeySet)
-        emailset = getUtility(IEmailAddressSet)
-        for key in sshkeyset.getByPeople(members):
-            members_by_id[key.personID].addSSHKey(key)
-        for key in gpgkeyset.getGPGKeysForPeople(members):
-            members_by_id[key.ownerID].addGPGKey(key)
-        for email in emailset.getPreferredEmailForPeople(members):
-            members_by_id[email.person.id].setPreferredEmail(email)
-        return members
-
     def __call__(self):
         """Render RDF output.
 

=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py	2010-09-22 22:51:48 +0000
+++ lib/lp/registry/browser/product.py	2010-09-23 17:41:09 +0000
@@ -155,6 +155,7 @@
 from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES
 from lp.code.browser.branchref import BranchRef
 from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
+from lp.registry.browser import BaseRdfView
 from lp.registry.browser.announcement import HasAnnouncementsView
 from lp.registry.browser.branding import BrandingChangeView
 from lp.registry.browser.distribution import UsesLaunchpadMixin
@@ -1406,7 +1407,7 @@
     def setUpFields(self):
         super(ProductConfigureBase, self).setUpFields()
         if self.usage_fieldname is not None:
-            # The usage fields are shared among pillars.  But when referring to
+            # The usage fields are shared among pillars. But when referring to
             # an individual object in Launchpad it is better to call it by its
             # real name, i.e. 'project' instead of 'pillar'.
             usage_field = self.form_fields.get(self.usage_fieldname)
@@ -1730,31 +1731,15 @@
     page_title = label
 
 
-class ProductRdfView:
+class ProductRdfView(BaseRdfView):
     """A view that sets its mime-type to application/rdf+xml"""
 
     template = ViewPageTemplateFile(
         '../templates/product-rdf.pt')
 
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
-
-    def __call__(self):
-        """Render RDF output, and return it as a string encoded in UTF-8.
-
-        Render the page template to produce RDF output.
-        The return value is string data encoded in UTF-8.
-
-        As a side-effect, HTTP headers are set for the mime type
-        and filename for download."""
-        self.request.response.setHeader('Content-Type', 'application/rdf+xml')
-        self.request.response.setHeader('Content-Disposition',
-                                        'attachment; filename=%s.rdf' %
-                                        self.context.name)
-        unicodedata = self.template()
-        encodeddata = unicodedata.encode('utf-8')
-        return encodeddata
+    @property
+    def filename(self):
+        return self.context.name
 
 
 class Icon:

=== modified file 'lib/lp/registry/browser/productrelease.py'
--- lib/lp/registry/browser/productrelease.py	2010-08-20 20:31:18 +0000
+++ lib/lp/registry/browser/productrelease.py	2010-09-23 17:41:09 +0000
@@ -50,6 +50,7 @@
 from canonical.lazr.utils import smartquote
 from canonical.widgets import DateTimeWidget
 from lp.registry.browser import (
+    BaseRdfView,
     MilestoneOverlayMixin,
     RegistryDeleteViewMixin,
     )
@@ -256,33 +257,17 @@
         return canonical_url(self.context)
 
 
-class ProductReleaseRdfView(object):
+class ProductReleaseRdfView(BaseRdfView):
     """A view that sets its mime-type to application/rdf+xml"""
 
     template = ViewPageTemplateFile('../templates/productrelease-rdf.pt')
 
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
-
-    def __call__(self):
-        """Render RDF output, and return it as a string encoded in UTF-8.
-
-        Render the page template to produce RDF output.
-        The return value is string data encoded in UTF-8.
-
-        As a side-effect, HTTP headers are set for the mime type
-        and filename for download."""
-        self.request.response.setHeader('Content-Type', 'application/rdf+xml')
-        self.request.response.setHeader(
-            'Content-Disposition',
-            'attachment; filename=%s-%s-%s.rdf' % (
-                self.context.product.name,
-                self.context.productseries.name,
-                self.context.version))
-        unicodedata = self.template()
-        encodeddata = unicodedata.encode('utf-8')
-        return encodeddata
+    @property
+    def filename(self):
+        return '%s-%s-%s' % (
+            self.context.product.name,
+            self.context.productseries.name,
+            self.context.version)
 
 
 class ProductReleaseAddDownloadFileView(LaunchpadFormView):

=== modified file 'lib/lp/registry/browser/productseries.py'
--- lib/lp/registry/browser/productseries.py	2010-09-15 13:25:01 +0000
+++ lib/lp/registry/browser/productseries.py	2010-09-23 17:41:09 +0000
@@ -119,6 +119,7 @@
     ICodeImportSet,
     )
 from lp.registry.browser import (
+    BaseRdfView,
     MilestoneOverlayMixin,
     RegistryDeleteViewMixin,
     StatusCount,
@@ -1259,32 +1260,15 @@
         self.next_url = canonical_url(self.context)
 
 
-class ProductSeriesRdfView(object):
+class ProductSeriesRdfView(BaseRdfView):
     """A view that sets its mime-type to application/rdf+xml"""
 
     template = ViewPageTemplateFile(
         '../templates/productseries-rdf.pt')
 
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
-
-    def __call__(self):
-        """Render RDF output, and return it as a string encoded in UTF-8.
-
-        Render the page template to produce RDF output.
-        The return value is string data encoded in UTF-8.
-
-        As a side-effect, HTTP headers are set for the mime type
-        and filename for download."""
-        self.request.response.setHeader('Content-Type', 'application/rdf+xml')
-        self.request.response.setHeader('Content-Disposition',
-                                        'attachment; filename=%s-%s.rdf' % (
-                                            self.context.product.name,
-                                            self.context.name))
-        unicodedata = self.template()
-        encodeddata = unicodedata.encode('utf-8')
-        return encodeddata
+    @property
+    def filename(self):
+        return '%s-%s' % (self.context.product.name, self.context.name)
 
 
 class ProductSeriesFileBugRedirect(LaunchpadView):

=== modified file 'lib/lp/registry/browser/project.py'
--- lib/lp/registry/browser/project.py	2010-08-24 10:45:57 +0000
+++ lib/lp/registry/browser/project.py	2010-09-23 17:41:09 +0000
@@ -72,6 +72,7 @@
 from lp.blueprints.browser.specificationtarget import (
     HasSpecificationsMenuMixin,
     )
+from lp.registry.browser import BaseRdfView
 from lp.registry.browser.announcement import HasAnnouncementsView
 from lp.registry.browser.branding import BrandingChangeView
 from lp.registry.browser.menu import (
@@ -592,31 +593,15 @@
     field_names = ['icon', 'logo', 'mugshot']
 
 
-class ProjectRdfView(object):
+class ProjectRdfView(BaseRdfView):
     """A view that sets its mime-type to application/rdf+xml"""
 
     template = ViewPageTemplateFile(
         '../templates/project-rdf.pt')
 
-    def __init__(self, context, request):
-        self.context = context
-        self.request = request
-
-    def __call__(self):
-        """Render RDF output, and return it as a string encoded in UTF-8.
-
-        Render the page template to produce RDF output.
-        The return value is string data encoded in UTF-8.
-
-        As a side-effect, HTTP headers are set for the mime type
-        and filename for download."""
-        self.request.response.setHeader('Content-Type', 'application/rdf+xml')
-        self.request.response.setHeader(
-            'Content-Disposition',
-            'attachment; filename=%s-project.rdf' % self.context.name)
-        unicodedata = self.template()
-        encodeddata = unicodedata.encode('utf-8')
-        return encodeddata
+    @property
+    def filename(self):
+        return '%s-project' % self.context.name
 
 
 class ProjectAddQuestionView(QuestionAddView):

=== modified file 'lib/lp/registry/stories/person/xx-person-rdf.txt'
--- lib/lp/registry/stories/person/xx-person-rdf.txt	2009-08-13 19:03:36 +0000
+++ lib/lp/registry/stories/person/xx-person-rdf.txt	2010-09-23 17:41:09 +0000
@@ -80,33 +80,14 @@
         <foaf:name>testing Spanish team</foaf:name>
         <foaf:nick>testing-spanish-team</foaf:nick>
         <foaf:member>
-            <foaf:Person>
-              <foaf:name>Carlos Perell\xf3 Mar\xedn</foaf:name>
-              <foaf:nick>carlos</foaf:nick>
-               <foaf:mbox_sha1sum>BCB3DF72647D7B136A3C33005318D63974179452</foaf:mbox_sha1sum>
-            </foaf:Person>
-        </foaf:member>
-        <foaf:member>
-          <foaf:Person>
-            <foaf:name>Foo Bar</foaf:name>
-            <foaf:nick>name16</foaf:nick>
-            <foaf:mbox_sha1sum>D248D2313390766929B6CEC214BD9B640F5EA7E7</foaf:mbox_sha1sum>
-            <wot:hasKey>
-              <wot:PubKey>
-                <wot:hex_id>12345678</wot:hex_id>
-    ...
-              </wot:PubKey>
-            </wot:hasKey>
-          </foaf:Person>
-        </foaf:member>
-        <foaf:member>
-          <foaf:Person>
-            <foaf:name>Mark Shuttleworth</foaf:name>
-            <foaf:nick>mark</foaf:nick>
-            <foaf:img rdf:resource="http://.../logo.png"/>
-            <foaf:img rdf:resource="http://.../mugshot.png"/>
-            <foaf:mbox_sha1sum>3CB9B47B0C0ADC68633D899805A9ADDF0045F922</foaf:mbox_sha1sum>
-            <lp:sshPubKey>AAAAB3NzaC1kc3MAAABBAL5VoWG5sy3CnLYeOw47L8m9A15hA/PzdX2u0B7c2Z1ktFPcEaEuKbLqKVSkXpYm7YwKj9y88A9Qm61CdvI0c50AAAAVAKGY0YON9dEFH3DzeVYHVEBGFGfVAAAAQCoe0RhBcefm4YiyQVwMAxwTlgySTk7FSk6GZ95EZ5Q8/OTdViTaalvGXaRIsBdaQamHEBB+Vek/VpnF1UGGm8YAAABAaCXDl0r1k93JhnMdF0ap4UJQ2/NnqCyoE8Xd5KdUWWwqwGdMzqB1NOeKN6ladIAXRggLc2E00UsnUXh3GE3Rgw==</lp:sshPubKey>
+            <lp:specifiedAt rdf:resource="/~carlos/+rdf"/>
+        </foaf:member>
+        <foaf:member>
+          <lp:specifiedAt rdf:resource="/~name16/+rdf"/>
+        </foaf:member>
+        <foaf:member>
+          <lp:specifiedAt rdf:resource="/~mark/+rdf"/>
+        </foaf:member>
     ...
       </foaf:Group>
     </rdf:RDF>
@@ -116,17 +97,13 @@
 
 Note how ascii and non-ascii names are rendered properly:
 
+    >>> anon_browser.open("http://launchpad.dev/~carlos/+rdf";)
     >>> from BeautifulSoup import BeautifulSoup, SoupStrainer
     >>> strainer = SoupStrainer(['foaf:name'])
     >>> soup = BeautifulSoup(anon_browser.contents, parseOnlyThese=strainer)
     >>> for tag in soup:
     ...   tag.renderContents()
-    'testing Spanish team'
     'Carlos Perell\xc3\xb3 Mar\xc3\xadn'
-    'Foo Bar'
-    'Mark Shuttleworth'
-    'Miroslav Kure'
-    'Valentina Commissari'
 
 If the team has no active members no <foaf:member> elements will be
 present:
@@ -150,17 +127,3 @@
         <foaf:nick>name21</foaf:nick>
       </foaf:Group>
     </rdf:RDF>
-
-The pages still work fine for teams whose members have not set up
-mugshots or images for them:
-
-    >>> anon_browser.open("http://launchpad.dev/~name18/+rdf";)
-    >>> strainer = SoupStrainer(['foaf:member'])
-    >>> soup = BeautifulSoup(anon_browser.contents, parseOnlyThese=strainer)
-    >>> len(soup)
-    6
-    >>> strainer = SoupStrainer(['foaf:img'])
-    >>> soup = BeautifulSoup(anon_browser.contents, parseOnlyThese=strainer)
-    >>> len(soup)
-    0
-

=== modified file 'lib/lp/registry/stories/product/xx-product-rdf.txt'
--- lib/lp/registry/stories/product/xx-product-rdf.txt	2009-06-12 16:36:02 +0000
+++ lib/lp/registry/stories/product/xx-product-rdf.txt	2010-09-23 17:41:09 +0000
@@ -13,12 +13,7 @@
       <lp:title>Mozilla Firefox</lp:title>
   ...
       <lp:owner>
-        <foaf:Person>
-          <foaf:name>Sample Person</foaf:name>
-          <foaf:nick>name12</foaf:nick>
-          <foaf:mbox_sha1sum>AF55D387F22F761CB32EC2A9C81F66220452C081</foaf:mbox_sha1sum>
-          <lp:sshPubKey>AAAAB3NzaC1kc3MAAA...SG1gBOiI=</lp:sshPubKey>
-        </foaf:Person>
+        <lp:specifiedAt rdf:resource="/~name12/+rdf"/>
       </lp:owner>
   ...
     </lp:Product>

=== modified file 'lib/lp/registry/stories/project/xx-project-rdf.txt'
--- lib/lp/registry/stories/project/xx-project-rdf.txt	2009-06-12 16:36:02 +0000
+++ lib/lp/registry/stories/project/xx-project-rdf.txt	2010-09-23 17:41:09 +0000
@@ -13,12 +13,7 @@
       <lp:title>The Mozilla Project</lp:title>
   ...
       <lp:owner>
-        <foaf:Person>
-          <foaf:name>Sample Person</foaf:name>
-          <foaf:nick>name12</foaf:nick>
-          <foaf:mbox_sha1sum>AF55D387F22F761CB32EC2A9C81F66220452C081</foaf:mbox_sha1sum>
-          <lp:sshPubKey>AAAAB3NzaC1kc3MAAA...SG1gBOiI=</lp:sshPubKey>
-        </foaf:Person>
+        <lp:specifiedAt rdf:resource="/~name12/+rdf"/>
       </lp:owner>
   ...
      </lp:Project>

=== modified file 'lib/lp/registry/templates/person-rdf-contents.pt'
--- lib/lp/registry/templates/person-rdf-contents.pt	2010-03-11 16:09:29 +0000
+++ lib/lp/registry/templates/person-rdf-contents.pt	2010-09-23 17:41:09 +0000
@@ -5,7 +5,6 @@
         xmlns:lp="https://launchpad.net/rdf/launchpad#";
         xmlns:wot="http://xmlns.com/wot/0.1/";>
   <tal:is-person condition="not:context/isTeam" define="person context">
-    <metal:display-person define-macro="display_person">
       <foaf:Person>
         <foaf:name tal:content="person/displayname">Display Name</foaf:name>
         <foaf:nick tal:content="person/name">Nick name</foaf:nick>
@@ -30,7 +29,6 @@
           <lp:sshPubKey tal:content="sshkey/keytext"></lp:sshPubKey>
         </tal:sshkeys>
       </foaf:Person>
-    </metal:display-person>
   </tal:is-person>
   <tal:public tal:condition="context/name|nothing">
     <foaf:Group tal:condition="context/isTeam">
@@ -44,8 +42,9 @@
                   tal:attributes="rdf:resource context/logo/http_url" />
         <foaf:img tal:condition="context/mugshot"
                   tal:attributes="rdf:resource context/mugshot/http_url" />
-        <foaf:member tal:repeat="person view/buildMemberData">
-          <metal:display-person use-macro="template/macros/display_person" />
+        <foaf:member tal:repeat="person context/allmembers">
+            <lp:specifiedAt tal:attributes="rdf:resource
+                string:${person/fmt:url}/+rdf" />
         </foaf:member>
     </foaf:Group>
   </tal:public>

=== modified file 'lib/lp/registry/templates/product-rdf.pt'
--- lib/lp/registry/templates/product-rdf.pt	2009-10-26 18:40:04 +0000
+++ lib/lp/registry/templates/product-rdf.pt	2010-09-23 17:41:09 +0000
@@ -23,7 +23,7 @@
         </lp:description>
         <lp:creationDate tal:content="context/datecreated/fmt:datetime">
             1970-01-01 00:00:00
-        </lp:creationDate> 
+        </lp:creationDate>
         <lp:homepage tal:attributes="rdf:resource context/homepageurl" />
         <lp:wiki tal:condition="context/wikiurl"
                  tal:attributes="rdf:resource context/wikiurl" />
@@ -55,7 +55,10 @@
                     string:${context/fmt:url}/${series/name}/+rdf" />
             </lp:ProductSeries>
         </lp:series>
-        <lp:owner tal:content="structure context/owner/@@+rdf-contents/template" />
+        <lp:owner>
+            <lp:specifiedAt tal:attributes="rdf:resource
+                string:${context/owner/fmt:url}/+rdf" />
+        </lp:owner>
         <lp:status tal:condition="context/active">Active</lp:status>
         <lp:status tal:condition="not:context/active">Inactive</lp:status>
     </lp:Product>

=== modified file 'lib/lp/registry/templates/productrelease-rdf.pt'
--- lib/lp/registry/templates/productrelease-rdf.pt	2009-07-17 17:59:07 +0000
+++ lib/lp/registry/templates/productrelease-rdf.pt	2010-09-23 17:41:09 +0000
@@ -24,13 +24,16 @@
         </lp:changelog>
         <lp:creationDate tal:content="context/datecreated/fmt:datetime">
             1970-01-01 00:00:00
-        </lp:creationDate> 
+        </lp:creationDate>
         <lp:inProductSeries>
             <lp:ProductSeries>
                 <lp:specifiedAt tal:attributes="rdf:resource
                     string:${context/product/fmt:url}/${context/productseries/name}/+rdf" />
             </lp:ProductSeries>
         </lp:inProductSeries>
-        <lp:owner tal:content="structure context/owner/@@+rdf-contents/template" />
+        <lp:owner>
+            <lp:specifiedAt tal:attributes="rdf:resource
+                string:${context/owner/fmt:url}/+rdf" />
+        </lp:owner>
     </lp:ProductRelease>
 </rdf:RDF>

=== modified file 'lib/lp/registry/templates/project-rdf.pt'
--- lib/lp/registry/templates/project-rdf.pt	2009-07-17 17:59:07 +0000
+++ lib/lp/registry/templates/project-rdf.pt	2010-09-23 17:41:09 +0000
@@ -23,7 +23,7 @@
         </lp:description>
         <lp:creationDate tal:content="context/datecreated/fmt:datetime">
             1970-01-01 00:00:00
-        </lp:creationDate> 
+        </lp:creationDate>
         <lp:homepage tal:attributes="rdf:resource context/homepageurl" />
         <lp:wiki tal:condition="context/wikiurl"
                  tal:attributes="rdf:resource context/wikiurl" />
@@ -41,7 +41,10 @@
                                                 string:${product/fmt:url}/+rdf" />
             </lp:Product>
         </lp:product>
-        <lp:owner tal:content="structure context/owner/@@+rdf-contents/template" />
+        <lp:owner>
+            <lp:specifiedAt tal:attributes="rdf:resource
+                string:${context/owner/fmt:url}/+rdf" />
+        </lp:owner>
         <lp:status tal:condition="context/active">Active</lp:status>
         <lp:status tal:condition="not:context/active">Inactive</lp:status>
     </lp:Project>