implement /packages/{name} as its own route

A few things added with this commit:

- aurweb.packages.util
    - A module providing package and pkgbase helpers.
- aurweb.template.register_filter
    - A decorator that can be used to register a filter:
      @register_filter("some_filter") def f(): pass

Additionally, template partials have been split off a bit
differently. Changes:

- /packages/{name} is defined in packages/show.html.
- partials/packages/package_actions.html is now
  partials/packages/actions.html.
- partials/packages/details.html has been added.
- partials/packages/comments.html has been added.
- partials/packages/comment.html has been added.
- models.dependency_type additions: name and id constants.
- models.relation_type additions: name and id constants.
- models.official_provider additions: base official url constant.

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2021-07-15 22:48:26 -07:00
parent 2d3d03e01e
commit ae3d302c47
22 changed files with 1166 additions and 254 deletions

View file

@ -0,0 +1,23 @@
{% extends "partials/layout.html" %}
{% block pageContent %}
{% include "partials/packages/search.html" %}
<div id="pkgdetails" class="box">
<h2>{{ 'Package Details' | tr }}: {{ pkgbase.Name }} {{ pkgbase.packages.first().Version }}</h2>
{% set result = pkgbase %}
{% include "partials/packages/actions.html" %}
{% set show_package_details = True %}
{% include "partials/packages/details.html" %}
<div id="metadata">
{% include "partials/packages/package_metadata.html" %}
</div>
</div>
{% set pkgname = result.Name %}
{% set pkgbase_id = result.ID %}
{% set comments = comments %}
{% include "partials/packages/comments.html" %}
{% endblock %}

View file

@ -0,0 +1,162 @@
<!--
This partial requires result.Name to render
-->
<div id="detailslinks" class="listing">
<div id="actionlist">
<h4>{{ "Package Actions" | tr }}</h4>
<ul class="small">
<li>
<a href="/cgit/aur.git/tree/PKGBUILD?h={{ result.Name }}">
{{ "View PKGBUILD" | tr }}
</a>
/
<a href="/cgit/aur.git/log/?h={{ result.Name }}">
{{ "View Changes" | tr }}
</a>
</li>
<li>
<a href="/cgit/aur.git/snapshot/{{ result.Name }}.tar.gz">
{{ "Download snapshot" | tr }}
</a>
<li>
<a href="https://wiki.archlinux.org/title/Special:Search?search={{ result.Name }}">
{{ "Search wiki" | tr }}
</a>
</li>
{% if not request.user.is_authenticated() %}
{% if not out_of_date %}
<li>
<a href="/pkgbase/{{ result.Name }}/flag/">
{{ "Flag package out-of-date" | tr }}
</a>
</li>
{% else %}
<li>
<span class="flagged">
{% set ood_ts = result.OutOfDateTS | dt | as_timezone(timezone) %}
{{
"Flagged out-of-date (%s)"
| tr | format(ood_ts.strftime("%Y-%m-%d"))
}}
</span>
</li>
{% endif %}
<li>
<a href="/login?next={{ request.url.path | urlencode }}">
{{ "Vote for this package" | tr }}
</a>
</li>
<li>
<a href="/login?next={{ request.url.path | urlencode }}">
{{ "Enable notifications" | tr }}
</a>
</li>
{% else %}
{% if not out_of_date %}
<li>
<a href="/pkgbase/{{ result.Name }}/flag/">
{{ "Flag package out-of-date" | tr }}
</a>
</li>
{% else %}
<li>
<span class="flagged">
{% set ood_ts = result.OutOfDateTS | dt | as_timezone(timezone) %}
{{
"Flagged out-of-date (%s)"
| tr | format(ood_ts.strftime("%Y-%m-%d"))
}}
</span>
</li>
<li>
<form action="/pkgbase/{{ result.Name }}/unflag" method="post">
<input class="button text-button"
type="submit"
name="do_UnFlag"
value="{{ 'Unflag package' | tr }}"
/>
</form>
</li>
{% endif %}
<li>
{% if not voted %}
<form action="/pkgbase/{{ result.Name }}/vote/" method="post">
<input type="submit"
class="button text-button"
name="do_Vote"
value="{{ 'Vote for this package' | tr }}" />
</form>
{% else %}
<form action="/pkgbase/{{ result.Name }}/unvote/" method="post">
<input type="submit"
class="button text-button"
name="do_UnVote"
value="{{ 'Remove vote' | tr }}" />
</form>
{% endif %}
</li>
<li>
{% if notified %}
<form action="/pkgbase/{{ result.Name }}/unnotify/" method="post">
<input type="submit"
class="button text-button"
name="do_UnNotify"
value="{{ 'Disable notifications' | tr }}"
/>
</form>
{% else %}
<form action="/pkgbase/{{ result.Name }}/notify/" method="post">
<input type="submit"
class="button text-button"
name="do_Notify"
value="{{ 'Enable notifications' | tr }}"
/>
</form>
{% endif %}
</li>
{% endif %}
</form>
{% if is_maintainer %}
<li>
<a href="/pkgbase/{{ result.Name }}/comaintainers/">
{{ "Manage Co-Maintainers" | tr }}
</a>
</li>
{% endif %}
<li><span class="flagged"></span></li>
<li>
{% if not request.user.is_authenticated() %}
<a href="/login?next={{ '/pkgbase/%s/request' | format(result.Name) | urlencode }}">
{{ "Submit Request" | tr }}
</a>
{% else %}
<a href="/pkgbase/{{ result.Name }}/request/">
{{ "Submit Request" | tr }}
</a>
{% endif %}
</li>
{% if is_maintainer %}
<li>
<a href="/pkgbase/{{ result.Name }}/delete/">
{{ "Delete Package" | tr }}
</a>
</li>
<li>
<a href="/pkgbase/{{ result.Name }}/merge/">
{{ "Merge Package" | tr }}
</a>
</li>
<li>
<form action="/pkgbase/{{ result.Name }}/disown/" method="post">
<input type="submit"
class="button text-button"
name="do_Disown"
value="{{ 'Disown Package' | tr }}"
/>
</form>
</li>
{% endif %}
</ul>
</div>
</div>

View file

@ -0,0 +1,48 @@
<h4 id="comment-{{ comment.ID }}" class="comment-header">
{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}
{% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %}
{{
"%s commented on %s" | tr | format(
('<a href="/account/%s" title="%s">%s</a>' | format(
comment.User.Username,
view_account_info,
comment.User.Username
)) if request.user.is_authenticated() else
(comment.User.Username),
'<a href="#comment-%s" class="date">%s</a>' | format(
comment.ID,
commented_at.strftime("%Y-%m-%d %H:%M")
)
)
| safe
}}
{% if is_maintainer %}
<form class="delete-comment-form" method="post" action="/pkgbase/{{ pkgname }}/">
<fieldset style="display:inline;">
<input type="hidden" name="action" value="do_DeleteComment" />
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
<input type="hidden" name="return_to" value="/pkgbase/{{ pkgname }}/"/>
<input type="image" class="delete-comment" src="/images/x.min.svg" width="11" height="11" alt="{{ 'Delete comment' | tr }}" title="{{ 'Delete comment' | tr }}" name="submit" value="1" />
</fieldset>
</form>
<a href="/pkgbase/{{ pkgname }}/edit-comment/?comment_id={{ comment.ID }}" class="edit-comment" title="Edit comment"><img src="/images/pencil.min.svg" alt="Edit comment" width="11" height="11"></a>
{% endif %}
<form class="pin-comment-form" method="post" action="/pkgbase/{{ pkgname }}/">
<fieldset style="display:inline;">
<input type="hidden" name="action" value="do_PinComment"/>
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
<input type="hidden" name="package_base" value="{{ pkgbase_id }}"/>
<input type="hidden" name="return_to" value="/pkgbase/{{ pkgname }}/"/>
<input type="image" class="pin-comment" src="/images/pin.min.svg" width="11" height="11" alt="{{ 'Pin comment' | tr }}" title="{{ 'Pin comment' | tr }}" name="submit" value="1"/>
</fieldset>
</form>
</h4>
<div id="comment-{{ comment.ID }}-content" class="article-content">
<div>
{% if comment.RenderedComment %}
{{ comment.RenderedComment | safe }}
{% else %}
{{ comment.Comments }}
{% endif %}
</div>
</div>

View file

@ -5,6 +5,7 @@
- comments (list)
-->
{% if request.user.is_authenticated() %}
<div id="generic-form" class="box">
<h2>Add Comment</h2>
<form action="/pkgbase/{{ pkgname }}/" method="post">
@ -31,61 +32,103 @@
</p>
<p>
<input type="submit" value="{{ 'Add Comment' | tr }}"/>
{% if not notifications_enabled %}
<span class="comment-enable-notifications">
<input id="id_enable_notifications"
type="checkbox"
name="enable_notifications"
/>
<label for="id_enable_notifications">
{{ "Enable notifications" | tr }}
</label>
</span>
{% endif %}
</p>
</fieldset>
</form>
</div>
{% endif %}
<div class="comments package-comments">
<div class="comments-header">
<h3>
<span class="text">{{ "Latest Comments" | tr }}</span>
<span class="arrow"></span>
</h3>
</div>
{% for comment in comments %}
<h4 id="comment-{{ comment.ID }}" class="comment-header">
{% set commented_at = comment.CommentTS | dt | as_timezone(timezone) %}
{% set view_account_info = 'View account information for %s' | tr | format(comment.User.Username) %}
{{
"%s commented on %s" | tr | format(
'<a href="/account/%s" title="%s">%s</a>' | format(
comment.User.Username,
view_account_info,
comment.User.Username
),
'<a href="#comment-%s" class="date">%s</a>' | format(
comment.ID,
commented_at.strftime("%Y-%m-%d %H:%M")
)
)
| safe
}}
{% if is_maintainer %}
<form class="delete-comment-form" method="post" action="/pkgbase/{{ pkgname }}/">
<fieldset style="display:inline;">
<input type="hidden" name="action" value="do_DeleteComment" />
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
<input type="hidden" name="return_to" value="/pkgbase/{{ pkgname }}/"/>
<input type="image" class="delete-comment" src="/images/x.min.svg" width="11" height="11" alt="{{ 'Delete comment' | tr }}" title="{{ 'Delete comment' | tr }}" name="submit" value="1" />
</fieldset>
</form>
<a href="/pkgbase/{{ pkgname }}/edit-comment/?comment_id={{ comment.ID }}" class="edit-comment" title="Edit comment"><img src="/images/pencil.min.svg" alt="Edit comment" width="11" height="11"></a>
{% endif %}
<form class="pin-comment-form" method="post" action="/pkgbase/{{ pkgname }}/">
<fieldset style="display:inline;">
<input type="hidden" name="action" value="do_PinComment"/>
<input type="hidden" name="comment_id" value="{{ comment.ID }}"/>
<input type="hidden" name="package_base" value="{{ pkgbase_id }}"/>
<input type="hidden" name="return_to" value="/pkgbase/{{ pkgname }}/"/>
<input type="image" class="pin-comment" src="/images/pin.min.svg" width="11" height="11" alt="{{ 'Pin comment' | tr }}" title="{{ 'Pin comment' | tr }}" name="submit" value="1"/>
</fieldset>
</form>
</h4>
<div id="comment-{{ comment.ID }}-content" class="article-content">
<div>
<p>{{ comment.RenderedComment | safe }}</p>
{% if comments.count() %}
<div class="comments package-comments">
<div class="comments-header">
<h3>
<span class="text">{{ "Latest Comments" | tr }}</span>
<span class="arrow"></span>
</h3>
</div>
{% for comment in comments.all() %}
{% include "partials/packages/comment.html" %}
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
<script type="text/javascript" nonce="{{ request.user.nonce }}">
function add_busy_indicator(sibling) {
const img = document.createElement('img');
img.src = "/images/ajax-loader.gif";
img.classList.add('ajax-loader');
img.style.height = 11;
img.style.width = 16;
img.alt = "Busy…";
sibling.insertAdjacentElement('afterend', img);
}
function remove_busy_indicator(sibling) {
const elem = sibling.nextElementSibling;
elem.parentNode.removeChild(elem);
}
function getParentsUntil(elem, className) {
// Limit to 10 depth
for ( ; elem && elem !== document; elem = elem.parentNode) {
if (elem.matches(className)) {
break;
}
}
return elem;
}
function handleEditCommentClick(event) {
event.preventDefault();
const parent_element = getParentsUntil(event.target, '.comment-header');
const parent_id = parent_element.id;
const comment_id = parent_id.substr(parent_id.indexOf('-') + 1);
// The div class="article-content" which contains the comment
const edit_form = parent_element.nextElementSibling;
const params = new URLSearchParams({
type: "get-comment-form",
arg: comment_id,
base_id: {{ pkgbase.ID }},
pkgbase_name: {{ pkgbase.Name }}
});
const url = '/rpc?' + params.toString();
add_busy_indicator(event.target);
fetch(url, {
method: 'GET'
})
.then(function(response) { return response.json(); })
.then(function(data) {
remove_busy_indicator(event.target);
if (data.success) {
edit_form.innerHTML = data.form;
edit_form.querySelector('textarea').focus();
} else {
alert(data.error);
}
});
}
document.addEventListener('DOMContentLoaded', function() {
const divs = document.querySelectorAll('.edit-comment');;
for (let div of divs) {
div.addEventListener('click', handleEditCommentClick);
}
});
</script>

View file

@ -0,0 +1,145 @@
<table id="pkginfo">
<tr>
<th>{{ "Git Clone URL" | tr }}:</th>
<td>
<a class="copy" href="{{ git_clone_uri_anon | format(pkgbase.Name) }}">{{ git_clone_uri_anon | format(pkgbase.Name) }}</a> ({{ "read-only" | tr }}, {{ "click to copy" | tr }})
{% if is_maintainer %}
<br /> <a class="copy" href="{{ git_clone_uri_priv | format(pkgbase.Name) }}">{{ git_clone_uri_priv | format(pkgbase.Name) }}</a> ({{ "click to copy" | tr }})
{% endif %}
</td>
</tr>
{% if show_package_details | default(False) %}
<tr>
<th>{{ "Package Base" | tr }}:</th>
<td class="wrap">
<a href="/pkgbase/{{ pkgbase.Name }}">
{{ pkgbase.Name }}
</a>
</td>
</tr>
<tr>
<th>{{ "Description" | tr }}:</th>
<td class="wrap">{{ pkgbase.packages.first().Description }}</td>
</tr>
<tr>
<th>{{ "Upstream URL" | tr }}:</th>
<td class="wrap">
{% set pkg = pkgbase.packages.first() %}
{% if pkg.URL %}
<a href="{{ pkg.URL }}">{{ pkg.URL }}</a>
{% else %}
{{ "None" | tr }}
{% endif %}
</td>
</tr>
{% endif %}
{% if pkgbase.keywords.count() %}
<tr>
<th>{{ "Keywords" | tr }}:</th>
{% if is_maintainer %}
<td>
<form method="update"
action="/pkgbase/{{ pkgbase.Name }}/keywords/"
>
<div>
<input type="text"
name="keywords"
value="{{ pkgbase.keywords | join(' ', attribute='Keyword') }}"
/>
<input type="submit" value="{{ 'Update' | tr }}"/>
</div>
</form>
</td>
{% else %}
<td>
{% for keyword in pkgbase.keywords %}
<a class="keyword"
href="/packages/?K={{ keyword.Keyword }}&amp;SB=p"
>
{{ keyword.Keyword }}
</a>
{% endfor %}
</td>
{% endif %}
</tr>
{% endif %}
{% if licenses and licenses.count() and show_package_details | default(False) %}
<tr>
<th>{{ "Licenses" | tr }}:</th>
<td>{{ licenses | join(', ', attribute='Name') | default('None' | tr) }} </td>
</tr>
{% endif %}
{% if show_package_details | default(False) %}
<tr>
<th>{{ "Conflicts" | tr }}:</th>
<td class="wrap">
{{ conflicts | join(', ', attribute='RelName') }}
</td>
</tr>
{% endif %}
<tr>
<th>{{ "Submitter" | tr }}:</th>
<td>
{% if request.user.is_authenticated() %}
<a href="/account/{{ pkgbase.Submitter.Username }}">
{{ pkgbase.Submitter.Username | default("None" | tr) }}
</a>
{% else %}
{{ pkgbase.Submitter.Username | default("None" | tr) }}
{% endif %}
</td>
</tr>
<tr>
<th>{{ "Maintainer" | tr }}:</th>
<td>
{% if request.user.is_authenticated() %}
<a href="/account/{{ pkgbase.Maintainer.Username }}">
{{ pkgbase.Maintainer.Username | default("None" | tr) }}
</a>
{% else %}
{{ pkgbase.Maintainer.Username | default("None" | tr) }}
{% endif %}
</td>
</tr>
<tr>
<th>{{ "Last Packager" | tr }}:</th>
<td>
{% if request.user.is_authenticated() %}
<a href="/account/{{ pkgbase.Packager.Username }}">
{{ pkgbase.Packager.Username | default("None" | tr) }}
</a>
{% else %}
{{ pkgbase.Packager.Username | default("None" | tr) }}
{% endif %}
</td>
</tr>
<tr>
<th>{{ "Votes" | tr }}:</th>
{% if not is_maintainer %}
<td>{{ pkgbase.package_votes.count() }}</td>
{% else %}
<td>
<a href="/pkgbase/{{ pkgbase.Name }}/voters">
{{ pkgbase.package_votes.count() }}
</a>
</td>
{% endif %}
</tr>
<tr>
<th>{{ "Popularity" | tr }}:</th>
<td>{{ pkgbase.Popularity | number_format(6 if pkgbase.Popularity <= 0.2 else 2) }}</td>
</tr>
<tr>
{% set submitted = pkgbase.SubmittedTS | dt | as_timezone(timezone) %}
<th>{{ "First Submitted" | tr }}:</th>
<td>{{ "%s" | format(submitted.strftime("%Y-%m-%d %H:%M")) }}</td>
</tr>
<tr>
<th>{{ "Last Updated" | tr }}:</th>
{% set updated = pkgbase.ModifiedTS | dt | as_timezone(timezone) %}
<td>{{ "%s" | format(updated.strftime("%Y-%m-%d %H:%M")) }}</td>
</tr>
</table>
<script type="text/javascript" src="/static/js/copy.js"></script>

View file

@ -1,87 +0,0 @@
<!--
This partial requires pkgname to render
-->
<div id="detailslinks" class="listing">
<div id="actionlist">
<h4>{{ "Package Actions" | tr }}</h4>
<ul class="small">
<li>
<a href="/cgit/aur.git/tree/PKGBUILD?h={{ pkgname }}">
{{ "View PKGBUILD" | tr }}
</a>
/
<a href="/cgit/aur.git/log/?h={{ pkgname }}">
{{ "View Changes" | tr }}
</a>
</li>
<li>
<a href="/cgit/aur.git/snapshot/{{ pkgname }}.tar.gz">
{{ "Download snapshot" | tr }}
</a>
<li>
<a href="https://wiki.archlinux.org/title/Special:Search?search={{ pkgname }}">
{{ "Search wiki" | tr }}
</a>
</li>
<li><span class="flagged"></span></li>
<li>
<a href="/pkgbase/{{ pkgname }}/flag/">
{{ "Flag package out-of-date" | tr }}
</a>
</li>
<li>
<form action="/pkgbase/{{ pkgname }}/vote/" method="post">
<input type="submit"
class="button text-button"
name="do_Vote"
value="{{ 'Vote for this package' | tr }}" />
</form>
</li>
<li>
<form action="/pkgbase/{{ pkgname }}/unnotify/" method="post">
<input type="submit"
class="button text-button"
name="do_UnNotify"
value="{{ 'Disable notifications' | tr }}"
/>
</form>
</li>
{% if is_maintainer %}
<li>
<a href="/pkgbase/{{ pkgname }}/comaintainers/">
{{ "Manage Co-Maintainers" | tr }}
</a>
</li>
{% endif %}
<li><span class="flagged"></span></li>
{% if request.user.is_authenticated() %}
<li>
<a href="/pkgbase/{{ pkgname }}/request/">
{{ "Submit Request" | tr }}
</a>
</li>
{% endif %}
{% if is_maintainer %}
<li>
<a href="/pkgbase/{{ pkgname }}/delete/">
{{ "Delete Package" | tr }}
</a>
</li>
<li>
<a href="/pkgbase/{{ pkgname }}/merge/">
{{ "Merge Package" | tr }}
</a>
</li>
<li>
<form action="/pkgbase/{{ pkgname }}/disown/" method="post">
<input type="submit"
class="button text-button"
name="do_Disown"
value="{{ 'Disown Package' | tr }}"
/>
</form>
</li>
{% endif %}
</ul>
</div>
</div>

View file

@ -0,0 +1,54 @@
<div id="pkgdeps" class="listing">
<h3>Dependencies ({{ dependencies.count() }})</h3>
<ul id="pkgdepslist">
{% for dep in dependencies.all() %}
<li>
{% set broken = not dep.is_package() %}
{% if broken %}
<span class="broken">
{% else %}
<a href="{{ dep.DepName | pkgname_link }}">
{% endif %}
{{ dep.DepName }}
{% if broken %}
</span>
{% else %}
</a>
{% endif %}
{{ dep.Package | provides_list(dep.DepName) | safe }}
{% set extra = dep | dep_extra %}
{% if extra %}
<em>{{ dep | dep_extra_desc }}</em>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<div id="pkgreqs" class="listing">
<h3>Required by ({{ required_by.count() }})</h3>
<ul id="pkgreqslist">
{% for depender in required_by.all() %}
<li>
<a href="{{ depender.Package | package_link }}">
{{ depender.Package.Name }}
</a>
<em>{{ depender | dep_extra }}</em>
</li>
{% endfor %}
</ul>
</div>
<div id="pkgfiles" class="listing">
<h3>Sources ({{ sources.count() }})</h3>
</div>
<div>
<ul id="pkgsrcslist">
{% for source in sources.all() %}
<li>
<a href="{{ source.Source }}">{{ source.Source }}</a>
</li>
{% endfor %}
</ul>
</div>

View file

@ -1,95 +0,0 @@
{% extends "partials/layout.html" %}
{% block pageContent %}
{% include "partials/packages/search.html" %}
<div id="pkgdetails" class="box">
<h2>Package Details: {{ pkgbase.Name }}</h2>
{% set result = pkgbase %}
{% set pkgname = "result.Name" %}
{% include "partials/packages/package_actions.html" %}
<table id="pkginfo">
<tr>
<th>{{ "Git Clone URL" | tr }}:</th>
<td>
<a class="copy" href="{{ git_clone_uri_anon | format(pkgbase.Name) }}">{{ git_clone_uri_anon | format(pkgbase.Name) }}</a> ({{ "read-only" | tr }}, {{ "click to copy" | tr }})
{% if is_maintainer %}
<br /> <a class="copy" href="{{ git_clone_uri_priv | format(pkgbase.Name) }}">{{ git_clone_uri_priv | format(pkgbase.Name) }}</a> ({{ "click to copy" | tr }})
{% endif %}
</td>
</tr>
<tr>
<th>{{ "Keywords" | tr }}:</th>
{% if is_maintainer %}
<td>
<form method="post" action="/pkgbase/{{ pkgbase.Name }}/">
<div>
<input type="hidden" name="action" value="do_SetKeywords" />
<input type="text" name="keywords" value="{{ pkgbase.keywords | join(' ', attribute='Keyword') }}"/>
<input type="submit" value="Update"/>
</div>
</form>
</td>
{% else %}
<td>
{% for item in pkgbase.keywords %}
<a class="keyword" href="/packages/?K={{ item.Keyword }}&amp;SB=p">{{ item.Keyword }}</a>
{% endfor %}
</td>
{% endif %}
</tr>
<tr>
<th>{{ "Submitter" | tr }}:</th>
<td>{{ pkgbase.Submitter.Username | default("None") }}</td>
</tr>
<tr>
<th>{{ "Maintainer" | tr }}:</th>
<td>{{ pkgbase.Maintainer.Username | default("None") }}</td>
</tr>
<tr>
<th>{{ "Last Packager" | tr }}:</th>
<td>{{ pkgbase.Packager.Username | default("None") }}</td>
</tr>
<tr>
<th>{{ "Votes" | tr }}:</th>
<td>{{ pkgbase.NumVotes }}</td>
</tr>
<tr>
<th>{{ "Popularity" | tr }}:</th>
<td>{{ '%0.2f' % pkgbase.Popularity | float }}</td>
</tr>
<tr>
{% set submitted = pkgbase.SubmittedTS | dt | as_timezone(timezone) %}
<th>{{ "First Submitted" | tr }}:</th>
<td>{{ "%s" | tr | format(submitted.strftime("%Y-%m-%d %H:%M")) }}</td>
</tr>
<tr>
<th>{{ "Last Updated" | tr }}:</th>
{% set updated = pkgbase.ModifiedTS | dt | as_timezone(timezone) %}
<td>{{ "%s" | tr | format(updated.strftime("%Y-%m-%d %H:%M")) }}</td>
</tr>
</table>
<div id="metadata">
<div id="pkgs" class="listing">
<!-- This needs to be replaced with the real implementation. -->
<h3>Packages ({{ packages_count }})</h3>
<ul>
{% for result in packages %}
<li>
<a href="/packages/{{ result.Name }}/"
title="{{ 'View packages details for' | tr }} {{ result.Name }}">
{{ result.Name }}
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% set pkgname = result.Name %}
{% set pkgbase_id = result.ID %}
{% set comments = comments %}
{% include "partials/packages/comments.html" %}
{% endblock %}