Revert "Revert "Update dashboard for apps (bug 693855)""

This reverts commit 8f2b7c17de.

Conflicts:

	apps/amo/context_processors.py
	apps/amo/tests/test_views.py
	media/css/impala/site.less
This commit is contained in:
Chris Van 2011-10-31 18:40:12 -07:00
Родитель c3475184a4
Коммит 57923a6b35
20 изменённых файлов: 626 добавлений и 167 удалений

Просмотреть файл

@ -74,6 +74,9 @@ def global_settings(request):
'href': reverse('devhub.submit.1')})
if waffle.flag_is_active(request, 'accept-webapps'):
if request.amo_user.is_developer:
tools_links.append({'text': _('Manage My Apps'),
'href': reverse('devhub.apps')})
tools_links.append({'text': _('Submit a New App'),
'href': reverse('devhub.submit_apps.1')})

Просмотреть файл

@ -116,8 +116,7 @@ class TestCommon(amo.tests.TestCase):
('Tools', '#'),
('Manage My Add-ons', reverse('devhub.addons')),
('Submit a New Add-on', reverse('devhub.submit.1')),
# TODO(robhudson): Uncomment this when the apps dashboard is live.
#('Manage My Apps', reverse('devhub.apps')),
('Manage My Apps', reverse('devhub.apps')),
('Submit a New App', reverse('devhub.submit_apps.1')),
('Developer Hub', reverse('devhub.index')),
]
@ -154,8 +153,7 @@ class TestCommon(amo.tests.TestCase):
('Tools', '#'),
('Manage My Add-ons', reverse('devhub.addons')),
('Submit a New Add-on', reverse('devhub.submit.1')),
# TODO(robhudson): Uncomment this when the apps dashboard is live.
#('Manage My Apps', reverse('devhub.apps')),
('Manage My Apps', reverse('devhub.apps')),
('Submit a New App', reverse('devhub.submit_apps.1')),
('Developer Hub', reverse('devhub.index')),
('Editor Tools', reverse('editors.home')),
@ -199,8 +197,7 @@ class TestCommon(amo.tests.TestCase):
('Tools', '#'),
('Manage My Add-ons', reverse('devhub.addons')),
('Submit a New Add-on', reverse('devhub.submit.1')),
# TODO(robhudson): Uncomment this when the apps dashboard is live.
#('Manage My Apps', reverse('devhub.apps')),
('Manage My Apps', reverse('devhub.apps')),
('Submit a New App', reverse('devhub.submit_apps.1')),
('Developer Hub', reverse('devhub.index')),
('Editor Tools', reverse('editors.home')),

Просмотреть файл

@ -9,7 +9,7 @@ from tower import ugettext as _, ungettext as ngettext
import amo
from amo.urlresolvers import reverse
from amo.helpers import breadcrumbs, page_title
from amo.helpers import breadcrumbs, impala_breadcrumbs, page_title
from access import acl
from addons.helpers import new_context
@ -46,7 +46,8 @@ def docs_page_title(context, title=None):
@register.function
@jinja2.contextfunction
def dev_breadcrumbs(context, addon=None, items=None, add_default=False):
def dev_breadcrumbs(context, addon=None, items=None, add_default=False,
impala=False):
"""
Wrapper function for ``breadcrumbs``. Prepends 'Developer Hub'
breadcrumbs.
@ -58,10 +59,12 @@ def dev_breadcrumbs(context, addon=None, items=None, add_default=False):
specified then the Add-on will be linked.
**add_default**
Prepends trail back to home when True. Default is False.
**impala**
Whether to use the impala_breadcrumbs helper. Default is False.
"""
crumbs = [(reverse('devhub.index'), _('Developer Hub'))]
if context.get('WEBAPPS'):
if context.get('webapp'):
title = _('My Apps')
else:
title = _('My Add-ons')
@ -80,7 +83,10 @@ def dev_breadcrumbs(context, addon=None, items=None, add_default=False):
crumbs.append((url, addon.name))
if items:
crumbs.extend(items)
return breadcrumbs(context, crumbs, add_default)
if impala:
return impala_breadcrumbs(context, crumbs, add_default)
else:
return breadcrumbs(context, crumbs, add_default)
@register.function

Просмотреть файл

@ -1,62 +1,71 @@
{% extends "devhub/base.html" %}
{% extends "devhub/base_impala.html" %}
{% block bodyclass %}{{ super() }} inverse{% endblock %}
{% if webapp %}
{% set title = _('Manage My Apps') %}
{% else %}
{% set title = _('Manage My Add-ons') %}
{% endif %}
{% block title %}{{ dev_page_title(_('Manage My Add-ons')) }}{% endblock %}
{% block title %}{{ dev_page_title(title) }}{% endblock %}
{% block content %}
<header>
{{ dev_breadcrumbs() }}
<hgroup>
<h2>{{ _('Manage My Add-ons') }}</h2>
<section class="primary">
<header class="hero">
{{ dev_breadcrumbs(impala=True) }}
<h1>{{ title }}</h1>
{% if addons %}
{% set cnt = addons.paginator.count %}
{% if webapp %}
{# L10n: {0} is an integer. #}
<h2>{{ ngettext('<b>{0}</b> app', '<b>{0}</b> apps', cnt)|f(cnt|numberfmt)|safe }}</h2>
{% else %}
{# L10n: {0} is an integer. #}
<h2>{{ ngettext('<b>{0}</b> add-on', '<b>{0}</b> add-ons', cnt)|f(cnt|numberfmt)|safe }}</h2>
{% endif %}
{% endif %}
</header>
</section>
{% if addons %}
{% set cnt = addons.paginator.count %}
{# L10n: {0} is an integer. #}
<h3>{{ ngettext('<b>{0}</b> add-on', '<b>{0}</b> add-ons',cnt)|f(cnt|numberfmt)|safe }}</h3>
{% endif %}
</hgroup>
</header>
{% if not addons %}
<div class="action-needed">
<h3>{{ _('Welcome to the Developer Dashboard') }}</h3>
<p>
{% trans %}
You don't currently have any add-ons hosted on Mozilla Add-ons. To learn
how the process works and submit your first add-on, click Get Started
below.
{% endtrans %}
</p>
<p class="button-wrapper">
<a href="{{ url('devhub.submit.1') }}"
class="action-button rounded">{{ _('Get Started') }}</a>
</p>
{% if webapp %}
<p>
{% trans %}
You don't currently have any apps hosted on Mozilla Marketplace. To
learn how the process works and submit your first app, click Get
Started below.
{% endtrans %}
</p>
<p class="button-wrapper">
<a href="{{ url('devhub.submit_apps.1') }}"
class="action-button rounded">{{ _('Get Started') }}</a>
</p>
{% else %}
<p>
{% trans %}
You don't currently have any add-ons hosted on Mozilla Add-ons. To learn
how the process works and submit your first add-on, click Get Started
below.
{% endtrans %}
</p>
<p class="button-wrapper">
<a href="{{ url('devhub.submit.1') }}"
class="action-button rounded">{{ _('Get Started') }}</a>
</p>
{% endif %}
</div>
{% else %}
<section id="dashboard" class="primary" role="main">
<div class="featured listing">
<div class="featured-inner">
{% set url_base = url('devhub.addons') %}
{% if addons.paginator.num_pages > 1 %}
{{ addon_listing_header(url_base, sort_opts, sorting) }}
{% endif %}
{{ dev_addon_listing_items(addons.object_list) }}
{% if addons.paginator.num_pages > 1 %}
<div class="listing-footer">
{{ addons|paginator }}
</div>
{% endif %}
</div>{# /featured-inner #}
</div>
</section>
<section id="dashboard-sidebar" class="secondary" role="complementary">
<aside id="devhub-sidebar" class="secondary" role="complementary">
<p id="submit-addon" class="cta">
<a href="{{ url('devhub.submit.1') }}"
class="button prominent">{{ _('Submit a New Add-on') }}</a>
{% if webapp %}
<a href="{{ url('devhub.submit_apps.1') }}"
class="button prominent">{{ _('Submit a New App') }}</a>
{% else %}
<a href="{{ url('devhub.submit.1') }}"
class="button prominent">{{ _('Submit a New Add-on') }}</a>
{% endif %}
</p>
<div class="recent-activity">
<h3>
@ -78,11 +87,34 @@
{% endfor %}
</ul>
{% endif %}
<p class="older-activity"><a href="{{ url('devhub.feed_all') }}">
{{ _('Older activity for My Add-ons') }} &#9658;</a></p>
{% if webapp %}
<p class="older-activity"><a href="{{ url('devhub.feed_all') }}">
{{ _('Older activity for My Apps') }} &#9658;</a></p>
{% else %}
<p class="older-activity"><a href="{{ url('devhub.feed_all') }}">
{{ _('Older activity for My Add-ons') }} &#9658;</a></p>
{% endif %}
</div>
{% include "devhub/includes/blog_posts.html" %}
{% if not webapp %}
{% include "devhub/includes/blog_posts.html" %}
{% endif %}
</aside>
<section id="dashboard" class="primary" role="main">
<div class="listing island hero c">
{% set url_base = url('devhub.apps') if webapp else url('devhub.addons') %}
{% if addons.paginator.num_pages > 1 %}
{{ impala_addon_listing_header(url_base, sort_opts, sorting) }}
{% endif %}
<div class="items">
{{ dev_addon_listing_items(addons.object_list) }}
</div>{# /items #}
{% if addons.paginator.num_pages > 1 %}
{{ addons|impala_paginator }}
{% endif %}
</div>
</section>
{% endif %}
{% endblock %}

Просмотреть файл

@ -0,0 +1,67 @@
<h5>{{ _('Actions') }}</h5>
<ul>
{% if addon.is_incomplete() %}
{% if check_addon_ownership(request, addon, dev=True) %}
<li>
<a href="{{ url('devhub.submit.resume', addon.slug) }}" class="tooltip"
title="{{ _("Resume the submission process for this app.")}}">
{{ _('Resume') }}</a>
</li>
{% endif %}
{% if check_addon_ownership(request, addon) %}
<li>
<a href="#" class="delete-addon tooltip"
title="{{ _('Delete this app.') }}">{{ _('Delete') }}</a>
<div class="modal-delete modal hidden">
{% include "devhub/addons/listing/delete_form.html" %}
</div>
</li>
{% endif %}
{% else %}
{% if check_addon_ownership(request, addon, dev=True) %}
<li>
<a href="{{ url('devhub.addons.edit', addon.slug) }}" class="tooltip"
title="{{ _("Edit details about this app's listing.") }}">
{{ _('Edit Listing') }}</a>
</li>
{% if addon.accepts_compatible_apps() and addon.current_version %}
<li class="compat"
data-src="{{ url('devhub.ajax.compat.status', addon.slug) }}">
{% include "devhub/addons/ajax_compat_status.html" %}
<div class="compat-error-popup popup error hidden"></div>
<div class="compat-update-modal modal hidden"></div>
</li>
{% endif %}
{% endif %}
{% if not addon.disabled_by_user %}
<li>
<a href="{{ locale_url('/statistics/addon/{0}')|f(addon.id) }}" class="tooltip"
{# <a href="{{ url('stats.overview', addon.slug) }}" class="tooltip" #}
title="{{ _('Daily statistics on downloads and users.') }}">
{{ _('Statistics') }}</a>
</li>
{% endif %}
<li>
<a href="#" class="more-actions">{{ _('More') }}</a>
<div class="more-actions-popup popup hidden">
{% set manage_urls = (
(url('devhub.addons.owner', addon.slug), _('Manage Authors & License')),
(url('devhub.addons.profile', addon.slug), _('Manage Developer Profile')),
(url('devhub.addons.payments', addon.slug), _('Manage Payments')),
(url('devhub.versions', addon.slug), _('Manage Status'))) %}
<ul>
{% for url, title in manage_urls %}
<li><a href="{{ url }}">{{ title }}</a></li>
{% endfor %}
</ul>
{% set view_urls = ((addon.get_url_path(), _('View App Listing')),
(url('devhub.feed', addon.slug), _('View Recent Changes'))) %}
<ul>
{% for url, title in view_urls %}
<li><a href="{{ url }}">{{ title }}</a></li>
{% endfor %}
</ul>
</div>
</li>
</ul>
{% endif %}

Просмотреть файл

@ -2,44 +2,52 @@
{% set collection = collection or None %}
{% set username = request.amo_user.username if request.user.is_authenticated() else '' %}
{% for addon in addons %}
<div class="item" data-addonid="{{ addon.id }}">
{{ dev_heading(addon, amo) }}
{% if addon.is_incomplete() %}
<p>
{% trans %}
This add-on will be deleted automatically after a few days if the
submission process is not completed.
{% endtrans %}
</p>
{% else %}
<div class="item-info">
{{ dev_item_info(addon, amo) }}
</div>
<ul class="item-details">
{% if addon.current_version %}
{% set link = url('devhub.versions.edit', addon.slug, addon.current_version.id) %}
{# L10n: {0} is a version number. #}
<li>{{ _('<strong>Latest version:</strong> <a href="{0}">{1}</a>'|
f(link, addon.current_version))|xssafe }}</li>
{% endif %}
{# L10n: {0} is a date. #}
<li>{{ _('<strong>Last updated:</strong> {0}'|
f(addon.last_updated|datetime))|safe }}</li>
<li id="version-status-item">
<strong>{{ _('Status:') }}</strong>
<a href="{{ url('devhub.versions', addon.slug) }}">
{% if addon.disabled_by_user %}
<span class="{{ status_class(addon) }}"><b>{{ _('Disabled') }}</b></span>
{% else %}
<span class="{{ status_class(addon) }}">
<b>{{ amo.STATUS_CHOICES[addon.status] }}</b></span>
<div class="item addon ignore-compatibility" data-addonid="{{ addon.id }}">
<div class="info">
{{ dev_heading(addon, amo) }}
{% if addon.is_incomplete() %}
<p>
{% trans %}
This submission will be deleted automatically after a few days if
the submission process is not completed.
{% endtrans %}
</p>
{% else %}
<div class="item-info">
{{ dev_item_info(addon, amo) }}
</div>
<ul class="item-details">
{% if not webapp %}
{% if addon.current_version %}
{% set link = url('devhub.versions.edit', addon.slug, addon.current_version.id) %}
{# L10n: {0} is a version number. #}
<li>{{ _('<strong>Latest version:</strong> <a href="{0}">{1}</a>'|
f(link, addon.current_version))|xssafe }}</li>
{% endif %}
</a>
</li>
</ul>
{% endif %}
<div class="item-actions">
{% include "devhub/addons/listing/item_actions.html" %}
{# L10n: {0} is a date. #}
<li>{{ _('<strong>Last updated:</strong> {0}'|
f(addon.last_updated|datetime))|safe }}</li>
{% endif %}
<li id="version-status-item">
<strong>{{ _('Status:') }}</strong>
<a href="{{ url('devhub.versions', addon.slug) }}">
{% if addon.disabled_by_user %}
<span class="{{ status_class(addon) }}"><b>{{ _('Disabled') }}</b></span>
{% else %}
<span class="{{ status_class(addon) }}">
<b>{{ amo.STATUS_CHOICES[addon.status] }}</b></span>
{% endif %}
</a>
</li>
</ul>
{% endif %}
<div class="item-actions">
{% if webapp %}
{% include "devhub/addons/listing/item_actions_app.html" %}
{% else %}
{% include "devhub/addons/listing/item_actions.html" %}
{% endif %}
</div>
</div>
</div>
{% endfor %}

Просмотреть файл

@ -1,10 +1,10 @@
{% macro dev_heading(addon, amo) %}
<h4>
<h3>
{% set is_complete = not addon.is_incomplete() %}
{% if is_complete %}<a href="{{ addon.get_url_path() }}">{% endif %}
<img class="icon" src="{{ addon.get_icon_url(64) }}">
{{ addon.name }}{% if is_complete %}</a>{% endif %}
</h4>
</h3>
{% endmacro %}
{% macro dev_item_info(addon, amo) %}

Просмотреть файл

@ -37,24 +37,45 @@
<nav id="site-nav" class="menu-nav no-img c">
<ul>
{% if request.user.is_authenticated() and request.amo_user.is_developer %}
<li class="top">
<a href="{{ url('devhub.addons') }}" class="controller">
{{ _('My Add-ons') }}</a>
<ul>
{% set my_addons = request.amo_user.addons.all()[:8] %}
{% for addon in my_addons %}
{% if loop.index == 8 %}
<li><a href="{{ url('devhub.addons') }}">
{{ _('more add-ons...') }}</a></li>
{% else %}
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">
{{ addon.name }}</a></li>
{% endif %}
{% endfor %}
<li><em><a href="{{ url('devhub.submit.1') }}">
{{ _('Submit a New Add-on') }}</a></em></li>
</ul>
</li>
{% if webapp %}
<li class="top">
<a href="{{ url('devhub.apps') }}" class="controller">
{{ _('My Apps') }}</a>
<ul>
{% set my_apps = request.amo_user.my_apps() %}
{% for addon in my_apps %}
{% if loop.index == 8 %}
<li><a href="{{ url('devhub.apps') }}">
{{ _('more apps...') }}</a></li>
{% else %}
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">
{{ addon.name }}</a></li>
{% endif %}
{% endfor %}
<li><em><a href="{{ url('devhub.submit_apps.1') }}">
{{ _('Submit a New App') }}</a></em></li>
</ul>
</li>
{% else %}
<li class="top">
<a href="{{ url('devhub.addons') }}" class="controller">
{{ _('My Add-ons') }}</a>
<ul>
{% set my_addons = request.amo_user.my_addons() %}
{% for addon in my_addons %}
{% if loop.index == 8 %}
<li><a href="{{ url('devhub.addons') }}">
{{ _('more add-ons...') }}</a></li>
{% else %}
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">
{{ addon.name }}</a></li>
{% endif %}
{% endfor %}
<li><em><a href="{{ url('devhub.submit.1') }}">
{{ _('Submit a New Add-on') }}</a></em></li>
</ul>
</li>
{% endif %}
{% endif %}
<li>
<a href="#" class="controller">{{ _('Documentation') }}</a>

Просмотреть файл

@ -16,40 +16,70 @@
{% endblock %}
{% block aux_nav %}
<li class="nomenu"><a class="return" href="{{ url('home') }}">
{{ _('Back to Add-ons') }}</a></li>
<li class="nomenu">
{% if webapp %}
<a class="return" href="{{ url('apps.home') }}">{{ _('Back to Apps') }}</a>
{% else %}
<a class="return" href="{{ url('home') }}">{{ _('Back to Add-ons') }}</a>
{% endif %}
</li>
{% endblock %}
{% block site_header_title %}
<h1 class="site-title prominent">
<a href="{{ url('devhub.index') }}" title="{{ _('Return to the DevHub homepage')|f(request.APP.pretty) }}">
{# L10n: Text in small tag is smaller and on it's own line #}
{% trans %}
<small>Add-on</small> Developer Hub
{% endtrans %}
{% if webapp %}
{% trans %}App Developer Preview{% endtrans %}
{% else %}
{# L10n: Text in small tag is smaller and on its own line #}
{% trans %}
<small>Add-on</small> Developer Hub
{% endtrans %}
{% endif %}
</a>
</h1>
<nav id="site-nav" class="menu-nav no-img c">
<ul>
{% if request.user.is_authenticated() and request.amo_user.is_developer %}
<li class="top">
<a href="{{ url('devhub.addons') }}" class="controller">
{{ _('My Add-ons') }}</a>
<ul>
{% set my_addons = request.amo_user.addons.all()[:8] %}
{% for addon in my_addons %}
{% if loop.index == 8 %}
<li><a href="{{ url('devhub.addons') }}">
{{ _('more add-ons...') }}</a></li>
{% else %}
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">
{{ addon.name }}</a></li>
{% endif %}
{% endfor %}
<li><em><a href="{{ url('devhub.submit.1') }}">
{{ _('Submit a New Add-on') }}</a></em></li>
</ul>
</li>
{% if webapp %}
<li class="top">
<a href="{{ url('devhub.apps') }}" class="controller">
{{ _('My Apps') }}</a>
<ul>
{% set my_addons = request.amo_user.my_apps() %}
{% for addon in my_addons %}
{% if loop.index == 8 %}
<li><a href="{{ url('devhub.apps') }}">
{{ _('more apps...') }}</a></li>
{% else %}
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">
{{ addon.name }}</a></li>
{% endif %}
{% endfor %}
<li><em><a href="{{ url('devhub.submit_apps.1') }}">
{{ _('Submit a New App') }}</a></em></li>
</ul>
</li>
{% else %}
<li class="top">
<a href="{{ url('devhub.addons') }}" class="controller">
{{ _('My Add-ons') }}</a>
<ul>
{% set my_addons = request.amo_user.my_addons() %}
{% for addon in my_addons %}
{% if loop.index == 8 %}
<li><a href="{{ url('devhub.addons') }}">
{{ _('more add-ons...') }}</a></li>
{% else %}
<li><a href="{{ url('devhub.addons.edit', addon.slug) }}">
{{ addon.name }}</a></li>
{% endif %}
{% endfor %}
<li><em><a href="{{ url('devhub.submit.1') }}">
{{ _('Submit a New Add-on') }}</a></em></li>
</ul>
</li>
{% endif %}
{% endif %}
<li>
<a href="#" class="controller">{{ _('Documentation') }}</a>

Просмотреть файл

@ -55,7 +55,8 @@ class MetaTests(amo.tests.TestCase):
class HubTest(amo.tests.TestCase):
fixtures = ['browse/nameless-addon', 'base/users']
fixtures = ['browse/nameless-addon', 'base/users',
'webapps/337141-steamcube']
def setUp(self):
self.url = reverse('devhub.index')
@ -68,13 +69,11 @@ class HubTest(amo.tests.TestCase):
ids = []
for i in range(num):
addon = Addon.objects.get(id=addon_id)
addon.id = addon.guid = None
addon.save()
AddonUser.objects.create(user=self.user_profile, addon=addon)
new_addon = Addon.objects.get(id=addon.id)
new_addon.name = str(addon.id)
new_addon.save()
ids.append(addon.id)
data = dict(type=addon.type, status=addon.status,
name='cloned-addon-%s-%s' % (addon_id, i))
new_addon = Addon.objects.create(**data)
AddonUser.objects.create(user=self.user_profile, addon=new_addon)
ids.append(new_addon.id)
return ids
@ -97,6 +96,8 @@ class TestNav(HubTest):
"""Check that the correct items are listed for the My Add-ons menu."""
# Assign this add-on to the current user profile.
addon = Addon.objects.get(id=57132)
addon.name = 'Test'
addon.save()
AddonUser.objects.create(user=self.user_profile, addon=addon)
r = self.client.get(self.url)
@ -175,16 +176,14 @@ class TestDashboard(HubTest):
r = self.client.get(self.url)
doc = pq(r.content)
eq_(len(doc('.item .item-info')), 10)
eq_(doc('#addon-list-options').length, 0)
eq_(doc('.listing-footer .pagination').length, 0)
eq_(doc('nav.paginator').length, 0)
# Create 5 add-ons.
self.clone_addon(5)
r = self.client.get(self.url + '?page=2')
doc = pq(r.content)
eq_(len(doc('.item .item-info')), 5)
eq_(doc('#addon-list-options').length, 1)
eq_(doc('.listing-footer .pagination').length, 1)
eq_(doc('nav.paginator').length, 1)
def test_show_hide_statistics(self):
a_pk = self.clone_addon(1)[0]
@ -208,8 +207,8 @@ class TestDashboard(HubTest):
doc = pq(r.content)
eq_(a.status, amo.STATUS_PUBLIC)
assert doc('.item[data-addonid=%s] ul.item-details' % a_pk)
assert doc('.item[data-addonid=%s] h4 a' % a_pk)
assert not doc('.item[data-addonid=%s] > p' % a_pk)
assert doc('.item[data-addonid=%s] h3 a' % a_pk)
assert not doc('.item[data-addonid=%s] > div.info > p' % a_pk)
def test_incomplete_addon_item(self):
a_pk = self.clone_addon(1)[0]
@ -217,8 +216,8 @@ class TestDashboard(HubTest):
r = self.client.get(self.url)
doc = pq(r.content)
assert not doc('.item[data-addonid=%s] ul.item-details' % a_pk)
assert not doc('.item[data-addonid=%s] h4 a' % a_pk)
assert doc('.item[data-addonid=%s] > p' % a_pk)
assert not doc('.item[data-addonid=%s] h3 a' % a_pk)
assert doc('.item[data-addonid=%s] > div.info > p' % a_pk)
def test_dev_news(self):
self.clone_addon(1) # We need one to see this module
@ -235,6 +234,43 @@ class TestDashboard(HubTest):
eq_(doc('.blog-posts li a').eq(4).text(), "hi 4")
class TestAppDashboard(HubTest):
def setUp(self):
super(TestAppDashboard, self).setUp()
self.url = reverse('devhub.apps')
waffle.models.Flag.objects.create(name='accept-webapps', everyone=True)
def test_app_dashboard(self):
eq_(self.client.get(self.url).status_code, 200)
def test_no_apps(self):
"""Check that no apps are displayed for this user."""
r = self.client.get(self.url)
doc = pq(r.content)
eq_(doc('.items .item').length, 0)
def test_app_pagination(self):
"""Check that the correct info. is displayed for each app:
namely, that apps are paginated at 10 items per page, and that
when there is more than one page, the 'Sort by' header and pagination
footer appear.
"""
# Create 10 add-ons.
self.clone_addon(10, addon_id=337141)
r = self.client.get(self.url)
doc = pq(r.content)
eq_(len(doc('.item .item-info')), 10)
eq_(doc('nav.paginator').length, 0)
# Create 5 add-ons.
self.clone_addon(5, addon_id=337141)
r = self.client.get(self.url + '?page=2')
doc = pq(r.content)
eq_(len(doc('.item .item-info')), 5)
eq_(doc('nav.paginator').length, 1)
class TestUpdateCompatibility(amo.tests.TestCase):
fixtures = ['base/apps', 'base/users', 'base/addon_4594_a9',
'base/addon_3615']

Просмотреть файл

@ -178,6 +178,7 @@ urlpatterns = decorate(write, patterns('',
# Redirect to /addons/ at the base.
url('^addon$', lambda r: redirect('devhub.addons', permanent=True)),
url('^addons$', views.dashboard, name='devhub.addons'),
url('^apps$', use_apps(views.dashboard), name='devhub.apps'),
url('^feed$', views.feed, name='devhub.feed_all'),
# TODO: not necessary when devhub homepage is moved out of remora
url('^feed/all$', lambda r: redirect('devhub.feed_all', permanent=True)),

Просмотреть файл

@ -51,11 +51,12 @@ from files.models import File, FileUpload, Platform
from files.utils import parse_addon
from market.models import AddonPremium
import paypal
from product_details import product_details
from search.views import BaseAjaxSearch
from translations.models import delete_translation
from users.models import UserProfile
from versions.models import Version
from product_details import product_details
from webapps.views import AppFilter
from zadmin.models import ValidationResult
from . import forms, tasks, feeds, signals
@ -76,10 +77,14 @@ class AddonFilter(BaseFilter):
('rating', _lazy(u'Rating')))
def addon_listing(request, addon_type, default='name'):
def addon_listing(request, default='name', webapp=False):
"""Set up the queryset and filtering for addon listing for Dashboard."""
qs = request.amo_user.addons.all()
filter = AddonFilter(request, qs, 'sort', default)
Filter = AppFilter if webapp else AddonFilter
if webapp:
qs = request.amo_user.addons.filter(type=amo.ADDON_WEBAPP)
else:
qs = request.amo_user.addons.exclude(type=amo.ADDON_WEBAPP)
filter = Filter(request, qs, 'sort', default)
return filter.qs, filter
@ -89,14 +94,16 @@ def index(request):
@login_required
def dashboard(request):
addons, filter = addon_listing(request, addon_type=amo.ADDON_ANY)
def dashboard(request, webapp=False):
addon_type = amo.ADDON_WEBAPP if webapp else amo.ADDON_ANY
addons, filter = addon_listing(request, webapp=webapp)
addons = amo.utils.paginate(request, addons, per_page=10)
blog_posts = _get_posts()
data = dict(addons=addons, sorting=filter.field,
items=_get_items(None, request.amo_user.addons.all())[:4],
sort_opts=filter.opts, rss=_get_rss_feed(request),
blog_posts=blog_posts, timestamp=int(time.time()))
blog_posts=blog_posts, timestamp=int(time.time()),
webapp=webapp)
return jingo.render(request, 'devhub/addons/dashboard.html', data)

Просмотреть файл

@ -23,6 +23,7 @@ import amo
import amo.models
from amo.urlresolvers import reverse
from translations.fields import PurifiedField
from translations.query import order_by_translation
log = commonware.log.getLogger('z.users')
@ -148,6 +149,18 @@ class UserProfile(amo.models.OnChangeMixin, amo.models.ModelBase):
return self.addons.reviewed().filter(addonuser__user=self,
addonuser__listed=True)
def my_addons(self, n=8):
"""Returns n addons (anything not a webapp)"""
qs = self.addons.exclude(type=amo.ADDON_WEBAPP)
qs = order_by_translation(qs, 'name')
return qs[:n]
def my_apps(self, n=8):
"""Returns n apps"""
qs = self.addons.filter(type=amo.ADDON_WEBAPP)
qs = order_by_translation(qs, 'name')
return qs[:n]
@property
def picture_dir(self):
split_id = re.match(r'((\d*?)(\d{0,3}?))\d{1,3}$', str(self.id))

Просмотреть файл

@ -1,4 +1,4 @@
from datetime import date
import datetime
import hashlib
from urlparse import urlparse
@ -82,11 +82,11 @@ class TestUserProfile(amo.tests.TestCase):
Test for a preview URL if image is set, or default image otherwise.
"""
u = UserProfile(id=1234, picture_type='image/png',
modified=date.today())
modified=datetime.date.today())
u.picture_url.index('/userpics/0/1/1234.png?modified=')
u = UserProfile(id=1234567890, picture_type='image/png',
modified=date.today())
modified=datetime.date.today())
u.picture_url.index('/userpics/1234/1234567/1234567890.png?modified=')
u = UserProfile(id=1234, picture_type=None)
@ -130,6 +130,28 @@ class TestUserProfile(amo.tests.TestCase):
addons = u.addons_listed.values_list('id', flat=True)
assert 3615 not in addons
def test_my_addons(self):
"""Test helper method to get N addons."""
addon1 = Addon.objects.create(name='test-1', type=amo.ADDON_EXTENSION)
AddonUser.objects.create(addon_id=addon1.id, user_id=2519, listed=True)
addon2 = Addon.objects.create(name='test-2', type=amo.ADDON_EXTENSION)
AddonUser.objects.create(addon_id=addon2.id, user_id=2519, listed=True)
u = UserProfile.objects.get(id=2519)
addons = u.my_addons()
self.assertTrue(sorted([a.name for a in addons]) == [addon1.name,
addon2.name])
def test_my_apps(self):
"""Test helper method to get N apps."""
addon1 = Addon.objects.create(name='test-1', type=amo.ADDON_WEBAPP)
AddonUser.objects.create(addon_id=addon1.id, user_id=2519, listed=True)
addon2 = Addon.objects.create(name='test-2', type=amo.ADDON_WEBAPP)
AddonUser.objects.create(addon_id=addon2.id, user_id=2519, listed=True)
u = UserProfile.objects.get(id=2519)
addons = u.my_apps()
self.assertTrue(sorted([a.name for a in addons]) == [addon1.name,
addon2.name])
def test_mobile_collection(self):
u = UserProfile.objects.get(id='4043307')
assert not Collection.objects.filter(author=u)

Просмотреть файл

@ -0,0 +1,135 @@
[
{
"pk": 337141,
"model": "addons.addon",
"fields": {
"dev_agreement": false,
"eula": null,
"last_updated": "2011-10-18 16:28:24",
"view_source": true,
"enable_thankyou": false,
"total_downloads": 0,
"premium_type": 0,
"app_slug": "something-something",
"developer_comments": null,
"_current_version": 1268829,
"average_daily_downloads": 0,
"_backup_version": null,
"manifest_url": "http://micropipes.com/temp/steamcube.webapp",
"admin_review_type": 1,
"the_future": null,
"trusted": false,
"total_contributions": null,
"locale_disambiguation": null,
"binary": false,
"guid": null,
"weekly_downloads": 0,
"support_url": null,
"disabled_by_user": false,
"paypal_id": "",
"average_rating": 0.0,
"wants_contributions": false,
"average_daily_users": 0,
"bayesian_rating": 0.0,
"share_count": 0,
"ts_slowness": null,
"homepage": null,
"support_email": null,
"public_stats": false,
"status": 4,
"privacy_policy": null,
"description": null,
"default_locale": "en-US",
"target_locale": null,
"suggested_amount": null,
"get_satisfaction_product": null,
"prerelease": false,
"thankyou_note": null,
"admin_review": false,
"auto_repackage": true,
"slug": "app-337141",
"external_software": false,
"highest_status": 4,
"get_satisfaction_company": null,
"name": null,
"created": "2011-10-18 16:28:24",
"type": 11,
"icon_type": "icon/games",
"annoying": 0,
"modified": "2011-10-18 16:29:46",
"summary": null,
"nomination_message": null,
"site_specific": false,
"charity": null,
"total_reviews": 0,
"the_reason": null,
"hotness": 0.0
}
},
{
"pk": 1268829,
"model": "versions.version",
"fields": {
"has_info_request": false,
"license": null,
"created": "2011-10-18 16:28:24",
"has_editor_comment": false,
"releasenotes": null,
"approvalnotes": "",
"modified": "2011-10-18 16:28:24",
"version": "1.0",
"version_int": 1000000200100,
"reviewed": null,
"nomination": null,
"addon": 337141
}
},
{
"pk": 2527085,
"model": "translations.translation",
"fields": {
"localized_string_clean": null,
"created": "2011-10-18 16:28:24",
"locale": "en-us",
"modified": "2011-10-18 16:28:57",
"id": 2425897,
"localized_string": "Something Something!"
}
},
{
"pk": 2527086,
"model": "translations.translation",
"fields": {
"localized_string_clean": "A simple 2.5D brain teaser block puzzle game. Find out how far can you get before time runs out?",
"created": "2011-10-18 16:28:24",
"locale": "en-US",
"modified": "2011-10-18 16:28:57",
"id": 2425898,
"localized_string": "A simple 2.5D brain teaser block puzzle game. Find out how far can you get before time runs out?"
}
},
{
"pk": 2527087,
"model": "translations.translation",
"fields": {
"localized_string_clean": "",
"created": "2011-10-18 16:28:57",
"locale": "en-us",
"modified": "2011-10-18 16:28:57",
"id": 2425899,
"localized_string": ""
}
},
{
"pk": 2425898,
"model": "translations.translation",
"fields": {
"localized_string_clean": "",
"created": "2011-07-26 14:16:26",
"locale": "en-us",
"modified": "2011-07-26 14:16:26",
"id": 2326782,
"localized_string": null
}
}
]

Просмотреть файл

@ -325,3 +325,6 @@
margin: .5em 0 .25em;
}
}
#dashboard .island {
float: left;
}

Просмотреть файл

@ -0,0 +1,72 @@
@import 'lib';
.listing .item {
.item-info {
border-left: 1px dotted @border-blue;
float: right;
margin-left: 2%;
padding-left: 2%;
width: 15em;
}
.item-info > .downloads,
.item-info > .users {
color: @green;
margin-top: 0;
}
.item-actions {
float: none;
a {
color: @note-gray;
&.more-actions {
display: block;
position: relative;
&:after {
border-color: @note-gray transparent transparent;
border-style: solid;
border-width: 3px 3px 0;
content: "";
margin: 5px 0 0 4px;
position: absolute;
height: 0;
top: 0;
width: 0;
}
}
}
h5 {
font-size: 1em;
margin-right: 4px;
}
h5, > ul, > ul > li {
color: @note-gray;
display: inline-block;
}
h5:after {
content: ":";
}
> ul > li {
display: inline-block;
&:not(:last-child):after {
background-color: @note-gray;
border-radius: 5em 5em 5em 5em;
content: "";
display: inline-block;
height: 2px;
margin: 0 0 4px 1px;
width: 2px;
}
}
}
&:hover {
a {
color: @link;
}
h5 {
color: @dark-gray;
}
}
}
.secondary .recent-activity li a {
display: inline;
padding: 0;
}

Просмотреть файл

@ -199,6 +199,9 @@
}
}
}
&:first-child .info {
border: 0;
}
h3 {
font-size: 16px;
font-weight: bold;
@ -314,6 +317,7 @@
.items + .paginator,
#sorter + .paginator,
#sorter + .listing-grid,
#sorter + .items,
#themes-listing .items {
border-top: 1px dotted @border-blue;
padding-top: 1em;

Просмотреть файл

@ -19,6 +19,7 @@ body {
margin: 0 auto;
position: relative;
min-height: @page-min-height;
> header h2,
> header h3 {
margin-bottom: 16px;
}

Просмотреть файл

@ -507,6 +507,7 @@ MINIFY_BUNDLES = {
),
'zamboni/devhub_impala': (
'css/impala/developers.less',
'css/impala/devhub-listing.less',
),
'zamboni/editors': (
'css/zamboni/editors.css',