Revert "Revert "pass dl-src/user client data to install/contrib/ratings (bug

757266, 763697, 756660)"" and fix migration
This commit is contained in:
Kevin Ngo 2012-07-20 11:43:02 -07:00
Родитель 3bb667dd82
Коммит 24903c76b7
28 изменённых файлов: 272 добавлений и 45 удалений

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

@ -46,6 +46,7 @@ class Review(amo.models.ModelBase):
editorreview = models.BooleanField(default=False)
flag = models.BooleanField(default=False)
sandbox = models.BooleanField(default=False)
client_data = models.ForeignKey('stats.ClientData', null=True, blank=True)
# Denormalized fields for easy lookup queries.
# TODO: index on addon, user, latest

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

@ -1,5 +1,6 @@
from collections import defaultdict
from django.conf import settings
from django.db.models import Q
from django.shortcuts import redirect
from django.utils.encoding import smart_str
@ -354,7 +355,7 @@ class BaseAjaxSearch(object):
class SearchSuggestionsAjax(BaseAjaxSearch):
src = 'ss'
src = 'mkt-ss' if settings.MARKETPLACE else 'ss'
class AddonSuggestionsAjax(SearchSuggestionsAjax):

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

@ -122,6 +122,7 @@ class Contribution(amo.models.ModelBase):
choices=do_dictsort(amo.PAYPAL_CURRENCIES),
default=amo.CURRENCY_DEFAULT)
source = models.CharField(max_length=255, null=True)
client_data = models.ForeignKey('stats.ClientData', null=True)
source_locale = models.CharField(max_length=10, null=True)
uuid = models.CharField(max_length=255, null=True)
@ -402,3 +403,20 @@ class GlobalStat(caching.base.CachingMixin, models.Model):
db_table = 'global_stats'
unique_together = ('name', 'date')
get_latest_by = 'date'
class ClientData(models.Model):
"""
Helps tracks user agent and download source data of installs and purchases.
"""
download_source = models.ForeignKey('zadmin.DownloadSource', null=True)
device_type = models.CharField(max_length=255)
user_agent = models.CharField(max_length=255)
is_chromeless = models.BooleanField()
language = models.CharField(max_length=7)
region = models.IntegerField(null=True)
class Meta:
db_table = 'client_data'
unique_together = ('download_source', 'device_type', 'user_agent',
'is_chromeless', 'language', 'region')

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

@ -13,8 +13,10 @@ z.capabilities = {
typeof navigator.mozApps.html5Implementation === 'undefined'
),
'fileAPI': !!window.FileReader,
'desktop': window.matchMedia('(max-width: 1024px)').matches,
'tablet': window.matchMedia('(max-width: 672px)').matches,
'userAgent': navigator.userAgent,
'desktop': window.matchMedia('(min-width: 673px)').matches,
'tablet': window.matchMedia('(max-width: 672px)').matches &&
window.matchMedia('(min-width: 601px)').matches,
'mobile': window.matchMedia('(max-width: 600px)').matches,
'touch': ('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch,
'nativeScroll': (function() {
@ -22,6 +24,9 @@ z.capabilities = {
})(),
'performance': !!(window.performance || window.msPerformance || window.webkitPerformance || window.mozPerformance)
};
z.capabilities.getDeviceType = function() {
return this.desktop ? 'desktop' : (this.tablet ? 'tablet' : 'mobile');
};
if (z.capabilities.tablet) {
// If we're on tablet, then we're not on desktop.
@ -34,14 +39,14 @@ if (z.capabilities.mobile) {
}
try {
if ('localStorage' in window && window['localStorage'] !== null) {
if ('localStorage' in window && window.localStorage !== null) {
z.capabilities.localStorage = true;
}
} catch (e) {
}
try {
if ('sessionStorage' in window && window['sessionStorage'] !== null) {
if ('sessionStorage' in window && window.sessionStorage !== null) {
z.capabilities.sessionStorage = true;
}
} catch (e) {

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

@ -49,15 +49,22 @@
function install(product, receipt) {
var data = {};
var post_data = {
device_type: z.capabilities.getDeviceType()
};
if (z.capabilities.chromeless) {
post_data.chromeless = 1;
}
$(window).trigger('app_install_start', product);
$.post(product.recordUrl).success(function(response) {
$.post(product.recordUrl, post_data).success(function(response) {
if (response.error) {
$('#pay-error').show().find('div').text(response.error);
installError(product);
return;
}
if (response.receipt) {
data['data'] = {'receipts': [response.receipt]};
data.data = {'receipts': [response.receipt]};
}
$.when(apps.install(product, data))
.done(installSuccess)

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

@ -7,9 +7,17 @@
$def,
message = $('#purchased-message'),
messageTemplate = template($('#purchased-template').html()),
data = {'currency': $('body').data('user').currency},
data = {
'currency': $('body').data('user').currency,
'src': z.getVars().src,
'device_type': z.capabilities.getDeviceType()
},
oneTimePayClicked = false;
if (z.capabilities.chromeless) {
data.chromeless = 1;
}
function beginPurchase(prod) {
if (!prod) return;
if ($def && $def.state() == 'pending') {

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

@ -0,0 +1,32 @@
INSERT INTO download_sources (name, type) VALUES
('mkt-home', 'full'),
('mkt-featured', 'full'),
('mkt-category', 'full'),
('mkt-detail', 'full'),
('mkt-detail-upsell', 'full'),
('mkt-search', 'full'),
('mkt-ss', 'full'),
('mkt-user-profile', 'full');
CREATE TABLE `client_data` (
`id` int(11) unsigned AUTO_INCREMENT NOT NULL PRIMARY KEY,
`download_source_id` int(11) unsigned NULL,
`device_type` varchar(255) NOT NULL,
`user_agent` varchar(255) NOT NULL,
`is_chromeless` bool,
`language` varchar(7) NOT NULL,
`region` int(11) unsigned NULL,
UNIQUE (`download_source_id`, `device_type`, `user_agent`, `is_chromeless`, `language`, `region`)
) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE `client_data` ADD CONSTRAINT `download_source_id_refs_id_b71b78fb` FOREIGN KEY (`download_source_id`) REFERENCES `download_sources` (`id`);
ALTER TABLE users_install ADD COLUMN client_data_id int(11) unsigned;
ALTER TABLE `users_install` ADD CONSTRAINT `client_data_id_refs_id_15062d7f` FOREIGN KEY (`client_data_id`) REFERENCES `client_data` (`id`);
ALTER TABLE stats_contributions ADD COLUMN client_data_id int(11) unsigned;
ALTER TABLE `stats_contributions` ADD CONSTRAINT `client_data_id_refs_id_c8ef1728` FOREIGN KEY (`client_data_id`) REFERENCES `client_data` (`id`);
ALTER TABLE reviews ADD COLUMN client_data_id int(11) unsigned;
ALTER TABLE `reviews` ADD CONSTRAINT `client_data_id_refs_id_d160c5ba` FOREIGN KEY (`client_data_id`) REFERENCES `client_data` (`id`);
ALTER TABLE `users_install` ADD CONSTRAINT UNIQUE (`addon_id`, `user_id`, `client_data_id`);

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

@ -83,7 +83,7 @@
<div id="my-submissions" class="listing c">
<h2>{{ _('My Submissions') }}</h2>
<ol class="items" start="{{ submissions.start_index() }}">
{{ search_results(submissions.object_list, src='search') }}
{{ search_results(submissions.object_list, src='mkt-user-profile') }}
</ol>
{{ submissions|impala_paginator }}
</div>

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

@ -197,6 +197,7 @@ JS = {
'js/zamboni/truncation.js',
'js/common/keys.js',
'js/mkt/capabilities.js',
'js/impala/serializers.js',
'js/mkt/fragments.js',
'js/mkt/recaptcha.js',
'js/mkt/overlay.js',
@ -209,7 +210,6 @@ JS = {
'js/mkt/apps.js',
'js/zamboni/outgoing_links.js',
'js/common/upload-image.js',
'js/impala/serializers.js',
# Search suggestions.
'js/impala/ajaxcache.js',
@ -238,6 +238,7 @@ JS = {
'js/lib/stick.js',
),
'mkt/reviewers': (
'js/mkt/utils.js',
'js/mkt/apps.js',
'js/mkt/install.js',
'js/mkt/buttons.js',

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

@ -26,18 +26,20 @@ def _categories():
@register.filter
@jinja2.contextfilter
def promo_grid(context, products):
def promo_grid(context, products, src=''):
t = env.get_template('browse/helpers/promo_grid.html')
return jinja2.Markup(t.render(request=context['request'],
products=products))
products=products,
src=src))
@register.filter
@jinja2.contextfilter
def promo_slider(context, products, feature=False):
def promo_slider(context, products, feature=False, src=''):
t = env.get_template('browse/helpers/promo_slider.html')
return jinja2.Markup(t.render(request=context['request'],
products=products, feature=feature))
products=products, feature=feature,
src=src))
@register.function

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

@ -3,7 +3,7 @@
<ul class="content">
{% for product in products %}
<li>
{{ market_tile(product) }}
{{ market_tile(product, src=src) }}
</li>
{% endfor %}
</ul>

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

@ -8,7 +8,7 @@
{% for product in products %}
{% if not preview or preview.filetype != 'video/webm' %}
<li>
{{ market_tile(product) }}
{{ market_tile(product, src=src) }}
</li>
{% endif %}
{% endfor %}

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

@ -24,7 +24,7 @@
</div>
</section>
<section class="featured full slider">
{{ featured|promo_slider(feature=True) }}
{{ featured|promo_slider(feature=True, src='mkt-category') }}
</section>
{% if popular %}
<section class="popular subheading full">
@ -37,7 +37,7 @@
</div>
</section>
<section class="popular grid full">
{{ popular|promo_grid }}
{{ popular|promo_grid(src='mkt-category') }}
</section>
{% else %}
{{ no_results() }}

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

@ -2,17 +2,13 @@ from nose import SkipTest
from nose.tools import eq_
from pyquery import PyQuery as pq
import waffle
import amo
import amo.tests
from amo.urlresolvers import reverse
from amo.utils import urlparams
from addons.models import Addon, AddonCategory, Category
from bandwagon.models import Collection, CollectionAddon
from addons.models import AddonCategory, Category
from mkt.webapps.models import Webapp
from mkt.zadmin.models import FeaturedApp
from users.models import UserProfile
class BrowseBase(amo.tests.ESTestCase):

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

@ -26,7 +26,7 @@
</div>
</section>
<section class="featured full slider">
{{ featured|promo_slider(feature=True) }}
{{ featured|promo_slider(feature=True, src='mkt-home') }}
</section>
{% if popular %}
<section class="popular tabbed narrow full">
@ -44,7 +44,7 @@
</div>
</section>
<section data-group="popular" data-shown class="popular grid full">
{{ popular|promo_grid }}
{{ popular|promo_grid(src='mkt-home') }}
</section>
<section data-group="popular" data-shown class="narrow popular view-more full">
<div>
@ -57,7 +57,7 @@
{{ _('New') }}</a></h2>
</section>
<section data-group="new" class="narrow popular grid full">
{{ latest|promo_grid }}
{{ latest|promo_grid(src='mkt-home') }}
</section>
<section data-group="new" class="narrow popular view-more full">
<div>

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

@ -2,6 +2,8 @@
from decimal import Decimal
import json
from django.conf import settings
import fudge
from fudge.inspector import arg
import mock
@ -16,10 +18,12 @@ from amo.urlresolvers import reverse
from devhub.models import AppLog
from market.models import (AddonPremium, AddonPurchase, PreApprovalUser,
Price, PriceCurrency)
from mkt import regions
from mkt.webapps.models import Webapp
from paypal import get_preapproval_url, PaypalError, PaypalDataError
from stats.models import Contribution
from users.models import UserProfile
from zadmin.models import DownloadSource
class TestPurchaseEmbedded(amo.tests.TestCase):
@ -321,6 +325,27 @@ class TestPurchaseEmbedded(amo.tests.TestCase):
self.client.post(self.addon.get_purchase_url(),
{'result_type': 'json'})
@mock.patch('paypal.get_paykey')
def test_contribution_client_data(self, get_paykey):
get_paykey.return_value = ['some-pay-key', '']
download_source = DownloadSource.objects.create(name='mkt-home')
device_type = 'desktop'
user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0)'
self.client.post_ajax(self.purchase_url,
data={'src': download_source.name,
'device_type': device_type,
'is_chromeless': False},
**{'HTTP_USER_AGENT': user_agent})
cons = Contribution.objects.filter(type=amo.CONTRIB_PENDING)
eq_(cons.count(), 1)
eq_(cons[0].client_data.download_source, download_source)
eq_(cons[0].client_data.device_type, device_type)
eq_(cons[0].client_data.user_agent, user_agent)
eq_(cons[0].client_data.is_chromeless, False)
eq_(not cons[0].client_data.language, False)
eq_(not cons[0].client_data.region, False)
class TestPurchaseDetails(amo.tests.TestCase):
fixtures = ['base/users', 'webapps/337141-steamcube']

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

@ -22,9 +22,11 @@ from lib.pay_server import client
from market.forms import PriceCurrencyForm
from market.models import AddonPurchase
import paypal
from stats.models import Contribution
from stats.models import ClientData, Contribution
import mkt
from mkt.account.views import preapproval as user_preapproval
from mkt.webapps.models import Webapp
from zadmin.models import DownloadSource
log = commonware.log.getLogger('z.purchase')
addon_view = addon_view_factory(qs=Webapp.objects.valid)
@ -40,7 +42,6 @@ def purchase(request, addon):
log.debug('Starting purchase of addon: %s by user: %s'
% (addon.pk, request.amo_user.pk))
amount = addon.premium.get_price()
source = request.POST.get('source', '')
uuid_ = hashlib.md5(str(uuid.uuid4())).hexdigest()
# L10n: {0} is the addon name.
contrib_for = (_(u'Mozilla Marketplace purchase of {0}')
@ -134,10 +135,31 @@ def purchase(request, addon):
if paykey:
# TODO(solitude): at some point we'll have to see what to do with
# contributions.
download_source = request.REQUEST.get('src', '')
contrib = Contribution(addon_id=addon.id, amount=amount,
source=source, source_locale=request.LANG,
source=download_source,
source_locale=request.LANG,
uuid=str(uuid_), type=amo.CONTRIB_PENDING,
paykey=paykey, user=request.amo_user)
# Grab a client data object to hook up with the Contribution object.
try:
download_source = DownloadSource.objects.get(name=download_source)
except DownloadSource.DoesNotExist:
download_source = None
try:
region = request.REGION.id
except AttributeError:
region = mkt.regions.WORLDWIDE.id
client_data, c = ClientData.objects.get_or_create(
download_source=download_source,
device_type=request.POST.get('device_type', ''),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
is_chromeless=request.POST.get('chromeless', False),
language=request.LANG,
region=region)
contrib.update(client_data=client_data)
log.debug('Storing contrib for uuid: %s' % uuid_)
# If this was a pre-approval, it's completed already, we'll

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

@ -7,10 +7,12 @@ import amo
from amo.helpers import numberfmt
import amo.tests
from reviews.models import Review, ReviewFlag
from stats.models import ClientData
from users.models import UserProfile
from zadmin.models import DownloadSource
from mkt.developers.models import ActivityLog
from mkt.webapps.models import Webapp
from mkt.webapps.models import Installed, Webapp
class ReviewTest(amo.tests.TestCase):
@ -262,6 +264,45 @@ class TestCreate(ReviewTest):
r = self.client.get(self.add)
self.assertLoginRedirects(r, self.add, 302)
def test_add_client_data(self):
self.enable_waffle()
client_data = ClientData.objects.create(
download_source=DownloadSource.objects.create(name='mkt-test'),
device_type='tablet', user_agent='test-agent', is_chromeless=False,
language='pt-BR', region=3
)
client_data_diff_agent = ClientData.objects.create(
download_source=DownloadSource.objects.create(name='mkt-test'),
device_type='tablet', user_agent='test-agent2',
is_chromeless=False, language='pt-BR', region=3
)
Installed.objects.create(user=self.user, addon=self.webapp,
client_data=client_data)
Installed.objects.create(user=self.user, addon=self.webapp,
client_data=client_data_diff_agent)
Review.objects.all().delete()
r = self.client.post(self.add,
{'body': 'x', 'rating': 4},
HTTP_USER_AGENT='test-agent')
review = Review.objects.order_by('-created')[0]
eq_(review.client_data, client_data)
def test_add_client_data_no_user_agent_match(self):
self.enable_waffle()
client_data = ClientData.objects.create(
download_source=DownloadSource.objects.create(name='mkt-test'),
device_type='tablet', user_agent='test-agent-1',
is_chromeless=False, language='pt-BR', region=3
)
Installed.objects.create(user=self.user, addon=self.webapp,
client_data=client_data)
Review.objects.all().delete()
r = self.client.post(self.add,
{'body': 'x', 'rating': 4},
HTTP_USER_AGENT='test-agent-2')
review = Review.objects.order_by('-created')[0]
eq_(review.client_data, client_data)
class TestListing(ReviewTest):

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

@ -19,9 +19,11 @@ from reviews.models import Review
from reviews.helpers import user_can_delete_review
from reviews.tasks import addon_review_aggregates
from reviews.views import get_flags
from stats.models import ClientData
from mkt.site import messages
from mkt.ratings.forms import ReviewForm
from mkt.webapps.models import Installed
log = commonware.log.getLogger('mkt.ratings')
@ -114,6 +116,26 @@ def add(request, addon):
# Don't let app owners review their own apps.
return http.HttpResponseForbidden()
# Get user agent of user submitting review. If there is an install with
# logged user agent that matches the current user agent, hook up that
# install's client data with the rating. If there aren't any install that
# match, use the most recent install. This implies that user must have an
# install to submit a review, but not sure if that logic is worked in, so
# default client_data to None.
client_data = None
user_agent = request.META.get('HTTP_USER_AGENT', '')
install = (Installed.objects.filter(user=request.user, addon=addon)
.order_by('-created'))
install_w_user_agent = (install.filter(client_data__user_agent=user_agent)
.order_by('-created'))
try:
if install_w_user_agent:
client_data = install_w_user_agent[0].client_data
elif install:
client_data = install[0].client_data
except ClientData.DoesNotExist:
client_data = None
data = request.POST or None
# Try to get an existing review of the app by this user if we can.
@ -149,11 +171,11 @@ def add(request, addon):
_('Your review was updated successfully!'))
else:
# If there isn't a review to overwrite, create a new review.
review = Review.objects.create(
review = Review.objects.create(client_data=client_data,
**_review_details(request, addon, form))
amo.log(amo.LOG.ADD_REVIEW, addon, review)
log.debug('[Review:%s] Created by user %s ' % (review.id,
request.user.id))
log.debug('[Review:%s] Created by user %s ' %
(review.id, request.user.id))
messages.success(request,
_('Your review was successfully added!'))

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

@ -13,6 +13,7 @@ from amo.urlresolvers import reverse
from devhub.models import AppLog
from mkt.webapps.models import Webapp
from users.models import UserProfile
from zadmin.models import DownloadSource
class TestReissue(amo.tests.TestCase):
@ -173,6 +174,28 @@ class TestInstall(amo.tests.TestCase):
content = json.loads(res.content)
assert content.get('receipt'), content
def test_installed_client_data(self):
download_source = DownloadSource.objects.create(name='mkt-home')
device_type = 'mobile'
user_agent = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0)'
self.addon.update(type=amo.ADDON_WEBAPP)
res = self.client.post(self.url,
data={'device_type': device_type,
'is_chromeless': False,
'src': download_source.name},
HTTP_USER_AGENT=user_agent)
eq_(res.status_code, 200)
eq_(self.user.installed_set.count(), 1)
ins = self.user.installed_set.get()
eq_(ins.client_data.download_source, download_source)
eq_(ins.client_data.device_type, device_type)
eq_(ins.client_data.user_agent, user_agent)
eq_(ins.client_data.is_chromeless, False)
eq_(not ins.client_data.language, False)
eq_(not ins.client_data.region, False)
class TestReceiptVerify(amo.tests.TestCase):
fixtures = ['base/users']

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

@ -24,7 +24,9 @@ from lib.cef_loggers import receipt_cef
import mkt
from mkt.webapps.models import Installed, Webapp
from services.verify import Verify
from stats.models import ClientData
from users.models import UserProfile
from zadmin.models import DownloadSource
from .utils import create_receipt
@ -70,8 +72,29 @@ def _record(request, addon):
not is_reviewer and not is_dev):
return http.HttpResponseForbidden()
# Log the install.
installed, c = Installed.objects.safer_get_or_create(addon=addon,
user=request.amo_user)
# Get download source from GET if it exists, if so get the download
# source object if it exists. Then grab a client data object to hook up
# with the Installed object.
download_source = DownloadSource.objects.filter(
name=request.REQUEST.get('src', None))
download_source = download_source[0] if download_source else None
try:
region = request.REGION.id
except AttributeError:
region = mkt.regions.WORLDWIDE.id
client_data, c = ClientData.objects.get_or_create(
download_source=download_source,
device_type=request.POST.get('device_type', ''),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
is_chromeless=request.POST.get('chromeless', False),
language=request.LANG,
region=region)
installed.update(client_data=client_data)
# Look up to see if its in the receipt cache and log if we have
# to recreate it.
receipt = memoize_get('create-receipt', installed.pk)

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

@ -13,7 +13,7 @@
{% endif %}
{% if pager.object_list %}
<ol class="items" start="{{ pager.start_index() }}">
{{ search_results(pager.object_list, field=query.sort, src='search') }}
{{ search_results(pager.object_list, field=query.sort, src='mkt-search') }}
</ol>
{{ pager|impala_paginator }}
{% else %}

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

@ -86,7 +86,7 @@ class TestWebappSearch(PaidAppMixin, SearchBase):
a = item.find('h3 a')
eq_(a.text(), unicode(self.webapp.name))
eq_(a.attr('href'),
urlparams(self.webapp.get_url_path(), src='search'))
urlparams(self.webapp.get_url_path(), src='mkt-search'))
def test_results_downloads(self):
for sort in ('', 'downloads', 'created'):

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

@ -120,14 +120,15 @@ def product_as_dict_theme(request, product):
@jinja2.contextfunction
@register.function
def market_tile(context, product):
def market_tile(context, product, src=''):
request = context['request']
if product.is_webapp():
classes = ['product', 'mkt-tile', 'arrow']
product_dict = product_as_dict(request, product)
data_attrs = {
'product': json.dumps(product_dict, cls=JSONEncoder),
'manifestUrl': product.manifest_url
'manifestUrl': product.manifest_url,
'src': src
}
if product.is_premium() and product.premium:
classes.append('premium')
@ -141,6 +142,7 @@ def market_tile(context, product):
product_dict = product_as_dict_theme(request, product)
data_attrs = {
'product': json.dumps(product_dict, cls=JSONEncoder),
'src': src
}
c = dict(product=product, data_attrs=data_attrs,
classes=' '.join(classes))

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

@ -3,7 +3,7 @@
{% set preview = previews[0].image_url if feature else
previews[0].thumbnail_url %}
{% endif %}
<a class="{{ classes }}" href="{{ product.get_url_path() }}" data-prefetch="1"
<a class="{{ classes }}" href="{{ product.get_url_path()|urlparams(src=data_attrs.src) }}" data-prefetch="1"
{% for k, v in data_attrs.items() -%}
data-{{ k }}="{{ v }}" {% endfor %}>
<div class="img icon" style="background-image:url({{ product.get_icon_url(64) }})"></div>

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

@ -1,7 +1,6 @@
import caching.base as caching
import jinja2
from jingo import env, register
import jinja2
import amo
from addons.models import AddonCategory, Category

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

@ -49,9 +49,8 @@ def _landing(request, category=None):
category = get_list_or_404(
Category.objects.filter(type=amo.ADDON_PERSONA,
slug=category))[0]
popular = (Addon.objects
popular = (Addon.objects.public()
.filter(type=amo.ADDON_PERSONA,
status=amo.STATUS_PUBLIC,
addoncategory__category__id=category.id)
.order_by('-persona__popularity')[:12])
@ -60,8 +59,7 @@ def _landing(request, category=None):
ids = AddonCategory.creatured_random(category, request.LANG)
featured = manual_order(base, ids, pk_name="addons.id")[:12]
else:
popular = (Addon.objects.public().filter(type=amo.ADDON_PERSONA,
status=amo.STATUS_PUBLIC)
popular = (Addon.objects.public().filter(type=amo.ADDON_PERSONA)
.order_by('-persona__popularity')[:12])
featured = get_featured_personas(request, num_personas=12)

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

@ -309,6 +309,7 @@ class Installed(amo.models.ModelBase):
addon = models.ForeignKey('addons.Addon', related_name='installed')
user = models.ForeignKey('users.UserProfile')
uuid = models.CharField(max_length=255, db_index=True, unique=True)
client_data = models.ForeignKey('stats.ClientData', null=True)
# Because the addon could change between free and premium,
# we need to store the state at time of install here.
premium_type = models.PositiveIntegerField(
@ -317,7 +318,7 @@ class Installed(amo.models.ModelBase):
class Meta:
db_table = 'users_install'
unique_together = ('addon', 'user')
unique_together = ('addon', 'user', 'client_data')
@receiver(models.signals.post_save, sender=Installed)