diff --git a/apps/addons/forms.py b/apps/addons/forms.py
index c70da334965..69dbb96e777 100644
--- a/apps/addons/forms.py
+++ b/apps/addons/forms.py
@@ -296,9 +296,6 @@ def __init__(self, *args, **kw):
cats = dict(self.addon.app_categories).get(app, [])
self.initial.append({'categories': [c.id for c in cats]})
- # Reconstruct the forms according to the initial data.
- self._construct_forms()
-
for app, form in zip(apps, self.forms):
key = app.id if app else None
form.request = self.request
diff --git a/apps/addons/models.py b/apps/addons/models.py
index 54a40fa3a03..47583d90ece 100644
--- a/apps/addons/models.py
+++ b/apps/addons/models.py
@@ -890,7 +890,7 @@ def compatible_version(self, app_id, app_version=None, platform=None,
version_id = 0
log.debug(u'Caching compat version %s => %s' % (cache_key, version_id))
- cache.set(cache_key, version_id, 0)
+ cache.set(cache_key, version_id, None)
return version
@@ -1356,7 +1356,7 @@ def tags_partitioned_by_developer(self):
"""Returns a tuple of developer tags and user tags for this addon."""
tags = self.tags.not_blacklisted()
if self.is_persona:
- return models.query.EmptyQuerySet(), tags
+ return [], tags
user_tags = tags.exclude(addon_tags__user__in=self.listed_authors)
dev_tags = tags.exclude(id__in=[t.id for t in user_tags])
return dev_tags, user_tags
@@ -1713,7 +1713,7 @@ def check_ownership(self, request, require_owner, require_author,
class AddonDeviceType(amo.models.ModelBase):
- addon = models.ForeignKey(Addon)
+ addon = models.ForeignKey(Addon, db_constraint=False)
device_type = models.PositiveIntegerField(
default=amo.DEVICE_DESKTOP, choices=do_dictsort(amo.DEVICE_TYPES),
db_index=True)
diff --git a/apps/addons/query.py b/apps/addons/query.py
index 7a2c941886e..d6179687893 100644
--- a/apps/addons/query.py
+++ b/apps/addons/query.py
@@ -77,11 +77,12 @@ def get_from_clause(self):
qn2 = self.connection.ops.quote_name
index_map = self.query.index_map
first = True
+ from_params = []
for alias in self.query.tables:
if not self.query.alias_refcount[alias]:
continue
try:
- name, alias, join_type, lhs, lhs_col, col, nullable = self.query.alias_map[alias]
+ name, alias, join_type, lhs, join_cols, _, join_field = self.query.alias_map[alias]
except KeyError:
# Extra tables can end up in self.tables, but not in the
# alias_map if they aren't in a join. That's OK. We skip them.
@@ -93,13 +94,25 @@ def get_from_clause(self):
else:
use_index = ''
if join_type and not first:
- # If you really need a LEFT OUTER JOIN, file a bug.
- join_type = 'INNER JOIN'
- result.append('%s %s%s %s ON (%s.%s = %s.%s)'
- % (join_type, qn(name), alias_str, use_index, qn(lhs),
- qn2(lhs_col), qn(alias), qn2(col)))
+ extra_cond = join_field.get_extra_restriction(
+ self.query.where_class, alias, lhs)
+ if extra_cond:
+ extra_sql, extra_params = extra_cond.as_sql(
+ qn, self.connection)
+ extra_sql = 'AND (%s)' % extra_sql
+ from_params.extend(extra_params)
+ else:
+ extra_sql = ""
+ result.append('%s %s%s %s ON ('
+ % (join_type, qn(name), alias_str, use_index))
+ for index, (lhs_col, rhs_col) in enumerate(join_cols):
+ if index != 0:
+ result.append(' AND ')
+ result.append('%s.%s = %s.%s' %
+ (qn(lhs), qn2(lhs_col), qn(alias), qn2(rhs_col)))
+ result.append('%s)' % extra_sql)
else:
- connector = not first and ', ' or ''
+ connector = connector = '' if first else ', '
result.append('%s%s%s %s' % (connector, qn(name), alias_str, use_index))
### jbalogh out. ###
first = False
@@ -112,4 +125,4 @@ def get_from_clause(self):
connector = not first and ', ' or ''
result.append('%s%s' % (connector, qn(alias)))
first = False
- return result, []
+ return result, from_params
diff --git a/apps/addons/tests/test_models.py b/apps/addons/tests/test_models.py
index 9a0e4f4becb..de87546095a 100644
--- a/apps/addons/tests/test_models.py
+++ b/apps/addons/tests/test_models.py
@@ -2200,29 +2200,32 @@ def setUp(self):
def test_extract(self):
File.objects.create(platform=self.platform_mob, version=self.version,
- filename=self.xpi_path('langpack-localepicker'))
- assert self.addon.get_localepicker()
+ filename=self.xpi_path('langpack-localepicker'),
+ status=amo.STATUS_PUBLIC)
+ assert self.addon.reload().get_localepicker()
assert 'title=Select a language' in self.addon.get_localepicker()
def test_extract_no_file(self):
File.objects.create(platform=self.platform_mob, version=self.version,
- filename=self.xpi_path('langpack'))
- eq_(self.addon.get_localepicker(), '')
+ filename=self.xpi_path('langpack'), status=amo.STATUS_PUBLIC)
+ eq_(self.addon.reload().get_localepicker(), '')
def test_extract_no_files(self):
eq_(self.addon.get_localepicker(), '')
def test_extract_not_language_pack(self):
File.objects.create(platform=self.platform_mob, version=self.version,
- filename=self.xpi_path('langpack-localepicker'))
- assert self.addon.get_localepicker()
+ filename=self.xpi_path('langpack-localepicker'),
+ status=amo.STATUS_PUBLIC)
+ assert self.addon.reload().get_localepicker()
self.addon.update(type=amo.ADDON_EXTENSION)
eq_(self.addon.get_localepicker(), '')
def test_extract_not_platform_mobile(self):
File.objects.create(platform=self.platform_all, version=self.version,
- filename=self.xpi_path('langpack-localepicker'))
- eq_(self.addon.get_localepicker(), '')
+ filename=self.xpi_path('langpack-localepicker'),
+ status=amo.STATUS_PUBLIC)
+ eq_(self.addon.reload().get_localepicker(), '')
class TestMarketplace(amo.tests.TestCase):
diff --git a/apps/amo/forms.py b/apps/amo/forms.py
index 3c4772116da..515ade24fc5 100644
--- a/apps/amo/forms.py
+++ b/apps/amo/forms.py
@@ -37,7 +37,7 @@ def _get_changed_data(self):
"""
Model = self._meta.model
if self._changed_data is None:
- changed = copy(super(AMOModelForm, self)._get_changed_data())
+ changed = copy(forms.ModelForm.changed_data.__get__(self))
fieldnames = [f.name for f in Model._meta.fields]
fields = [(name, Model._meta.get_field(name))
for name in changed if name in fieldnames]
diff --git a/apps/amo/models.py b/apps/amo/models.py
index 6f4e78c03e7..10ab38a82ae 100644
--- a/apps/amo/models.py
+++ b/apps/amo/models.py
@@ -163,8 +163,8 @@ class ManagerBase(caching.base.CachingManager, UncachedManagerBase):
function.
"""
- def get_query_set(self):
- qs = super(ManagerBase, self).get_query_set()
+ def get_queryset(self):
+ qs = super(ManagerBase, self).get_queryset()
if getattr(_locals, 'skip_cache', False):
qs = qs.no_cache()
return self._with_translations(qs)
diff --git a/apps/amo/templates/amo/robots.html b/apps/amo/templates/amo/robots.html
index b76f35378a6..284417ec973 100644
--- a/apps/amo/templates/amo/robots.html
+++ b/apps/amo/templates/amo/robots.html
@@ -16,7 +16,7 @@
{% for a in apps -%}
Disallow: /{{ l }}/{{ a.short }}{{ url('search.search', add_prefix=False) }}
-Disallow: /{{ l }}/{{ a.short }}{{ url('users.pwreset', add_prefix=False) }}
+Disallow: /{{ l }}/{{ a.short }}{{ url('password_reset_form', add_prefix=False) }}
{% endfor %}
{% endfor %}
diff --git a/apps/amo/tests/test_helpers.py b/apps/amo/tests/test_helpers.py
index 7a7c7826214..868422fa8cc 100644
--- a/apps/amo/tests/test_helpers.py
+++ b/apps/amo/tests/test_helpers.py
@@ -461,7 +461,7 @@ def test_absolutify():
def test_timesince():
month_ago = datetime.now() - timedelta(days=30)
- eq_(helpers.timesince(month_ago), u'1 month ago')
+ eq_(helpers.timesince(month_ago), u'1 month ago')
eq_(helpers.timesince(None), u'')
diff --git a/apps/amo/tests/test_readonly.py b/apps/amo/tests/test_readonly.py
index c388a88a561..2fd8303e1f3 100644
--- a/apps/amo/tests/test_readonly.py
+++ b/apps/amo/tests/test_readonly.py
@@ -3,7 +3,6 @@
from django.utils import importlib
import MySQLdb as mysql
-from lib.misc import safe_signals
from nose.tools import assert_raises, eq_
from pyquery import PyQuery as pq
@@ -30,7 +29,6 @@ class ReadOnlyModeTest(amo.tests.TestCase):
extra = ('amo.middleware.ReadOnlyMiddleware',)
def setUp(self):
- safe_signals.Signal.send = safe_signals.unsafe_send
models.signals.pre_save.connect(self.db_error)
models.signals.pre_delete.connect(self.db_error)
self.old_settings = dict((k, quickcopy(getattr(settings, k)))
@@ -52,7 +50,6 @@ def tearDown(self):
pass
models.signals.pre_save.disconnect(self.db_error)
models.signals.pre_delete.disconnect(self.db_error)
- safe_signals.Signal.send = safe_signals.safe_send
def db_error(self, *args, **kwargs):
raise mysql.OperationalError("You can't do this in read-only mode.")
diff --git a/apps/amo/tests/test_send_mail.py b/apps/amo/tests/test_send_mail.py
index bda55001f32..7138f981c23 100644
--- a/apps/amo/tests/test_send_mail.py
+++ b/apps/amo/tests/test_send_mail.py
@@ -26,6 +26,7 @@ def setUp(self):
self._email_blacklist = list(getattr(settings, 'EMAIL_BLACKLIST', []))
def tearDown(self):
+ translation.activate('en_US')
settings.EMAIL_BLACKLIST = self._email_blacklist
def test_send_string(self):
@@ -202,7 +203,7 @@ def test_send_html_mail_jinja(self):
def test_send_attachment(self):
path = os.path.join(ATTACHMENTS_DIR, 'bacon.txt')
- attachments = [(os.path.basename(path), storage.open(path),
+ attachments = [(os.path.basename(path), storage.open(path).read(),
mimetypes.guess_type(path)[0])]
send_mail('test subject', 'test body', from_email='a@example.com',
recipient_list=['b@example.com'], attachments=attachments)
diff --git a/apps/amo/utils.py b/apps/amo/utils.py
index 068262d3f3d..d01cb8265d2 100644
--- a/apps/amo/utils.py
+++ b/apps/amo/utils.py
@@ -671,12 +671,12 @@ def cache_ns_key(namespace, increment=False):
except ValueError:
log.info('Cache increment failed for key: %s. Resetting.' % ns_key)
ns_val = epoch(datetime.datetime.now())
- cache.set(ns_key, ns_val, 0)
+ cache.set(ns_key, ns_val, None)
else:
ns_val = cache.get(ns_key)
if ns_val is None:
ns_val = epoch(datetime.datetime.now())
- cache.set(ns_key, ns_val, 0)
+ cache.set(ns_key, ns_val, None)
return '%s:%s' % (ns_val, ns_key)
diff --git a/apps/api/tests/test_urls.py b/apps/api/tests/test_urls.py
new file mode 100644
index 00000000000..9d5a28aa887
--- /dev/null
+++ b/apps/api/tests/test_urls.py
@@ -0,0 +1,15 @@
+from mock import Mock
+from nose.tools import eq_
+
+from amo.tests import TestCase
+
+from ..urls import SwitchToDRF
+
+
+class TestDRFSwitch(TestCase):
+
+ def test_piston_view(self):
+ view = SwitchToDRF('LanguageView')
+ eq_(view(Mock(), 1).__module__, 'django.http.response')
+ self.create_switch('drf', db=True)
+ eq_(view(Mock()).__module__, 'rest_framework.response')
diff --git a/apps/api/tests/test_views.py b/apps/api/tests/test_views.py
index 85e1c82307b..85c2e9b2d34 100644
--- a/apps/api/tests/test_views.py
+++ b/apps/api/tests/test_views.py
@@ -1282,7 +1282,8 @@ def test_search_no_localepicker(self):
def setup_localepicker(self, platform):
self.addon.update(type=amo.ADDON_LPAPP, status=amo.STATUS_PUBLIC)
version = self.addon.versions.all()[0]
- File.objects.create(version=version, platform_id=platform)
+ File.objects.create(version=version, platform_id=platform,
+ status=amo.STATUS_PUBLIC)
def test_search_wrong_platform(self):
self.setup_localepicker(amo.PLATFORM_MAC.id)
diff --git a/apps/browse/views.py b/apps/browse/views.py
index 6676be8b3be..2fecf9780f6 100644
--- a/apps/browse/views.py
+++ b/apps/browse/views.py
@@ -294,17 +294,13 @@ class PersonasFilter(BaseFilter):
def _filter(self, field):
qs = Addon.objects
if field == 'created':
- return (qs.order_by('-created')
- .with_index(addons='created_type_idx'))
+ return qs.order_by('-created')
elif field == 'popular':
- return (qs.order_by('-persona__popularity')
- .with_index(personas='personas_popularity_idx'))
+ return qs.order_by('-persona__popularity')
elif field == 'rating':
- return (qs.order_by('-bayesian_rating')
- .with_index(addons='rating_type_idx'))
+ return qs.order_by('-bayesian_rating')
else:
- return (qs.order_by('-persona__movers')
- .with_index(personas='personas_movers_idx'))
+ return qs.order_by('-persona__movers')
def personas_listing(request, category_slug=None):
diff --git a/apps/devhub/forms.py b/apps/devhub/forms.py
index a2116140c0d..3f37e16fb72 100644
--- a/apps/devhub/forms.py
+++ b/apps/devhub/forms.py
@@ -441,7 +441,11 @@ def __init__(self, *args, **kw):
self.initial = ([{} for _ in qs] +
[{'application': a.id} for a in apps])
self.extra = len(amo.APP_GUIDS) - len(self.forms)
- self._construct_forms()
+ # After these changes, the forms need to be rebuilt. `forms`
+ # is a cached property, so we delete the existing cache and
+ # ask for a new one to be built.
+ del self.forms
+ self.forms
def clean(self):
if any(self.errors):
@@ -898,7 +902,6 @@ class PackagerCompatBaseFormSet(BaseFormSet):
def __init__(self, *args, **kw):
super(PackagerCompatBaseFormSet, self).__init__(*args, **kw)
self.initial = [{'application': a} for a in amo.APP_USAGE]
- self._construct_forms()
def clean(self):
if any(self.errors):
diff --git a/apps/devhub/models.py b/apps/devhub/models.py
index b25d18da32a..3e653e415b9 100644
--- a/apps/devhub/models.py
+++ b/apps/devhub/models.py
@@ -113,7 +113,7 @@ class AppLog(amo.models.ModelBase):
"""
This table is for indexing the activity log by app.
"""
- addon = models.ForeignKey(Webapp)
+ addon = models.ForeignKey(Webapp, db_constraint=False)
activity_log = models.ForeignKey('ActivityLog')
class Meta:
diff --git a/apps/devhub/tests/test_forms.py b/apps/devhub/tests/test_forms.py
index cd70fbe92d0..19fb1dcde5a 100644
--- a/apps/devhub/tests/test_forms.py
+++ b/apps/devhub/tests/test_forms.py
@@ -498,6 +498,7 @@ def test_localize_name_description(self):
def test_reupload(self, save_persona_image_mock,
create_persona_preview_images_mock,
make_checksum_mock):
+ make_checksum_mock.return_value = 'checksumbeforeyouwrecksome'
data = self.get_dict(header_hash='y0l0', footer_hash='abab')
self.form = EditThemeForm(data, request=self.request,
instance=self.instance)
diff --git a/apps/discovery/tests/test_views.py b/apps/discovery/tests/test_views.py
index 773080833ca..2d07ba82951 100644
--- a/apps/discovery/tests/test_views.py
+++ b/apps/discovery/tests/test_views.py
@@ -527,7 +527,8 @@ def test_eula_trickle(self):
class TestMonthlyPick(amo.tests.TestCase):
- fixtures = ['base/apps', 'base/addon_3615', 'discovery/discoverymodules']
+ fixtures = ['base/users', 'base/apps', 'base/addon_3615',
+ 'discovery/discoverymodules']
def setUp(self):
self.url = reverse('discovery.pane.promos', args=['Darwin', '10.0'])
diff --git a/apps/editors/helpers.py b/apps/editors/helpers.py
index b27d6edd54a..d01de125b1b 100644
--- a/apps/editors/helpers.py
+++ b/apps/editors/helpers.py
@@ -641,7 +641,7 @@ class ReviewAddon(ReviewBase):
def __init__(self, *args, **kwargs):
super(ReviewAddon, self).__init__(*args, **kwargs)
- self.is_upgrade = (self.addon.status is amo.STATUS_LITE_AND_NOMINATED
+ self.is_upgrade = (self.addon.status == amo.STATUS_LITE_AND_NOMINATED
and self.review_type == 'nominated')
def set_data(self, data):
diff --git a/apps/editors/models.py b/apps/editors/models.py
index 976fa8499d1..39d301bc16a 100644
--- a/apps/editors/models.py
+++ b/apps/editors/models.py
@@ -453,7 +453,7 @@ def get_total(cls, user):
if val is None:
val = 0
- cache.set(key, val, 0)
+ cache.set(key, val, None)
return val
@classmethod
@@ -469,7 +469,7 @@ def get_recent(cls, user, limit=5, addon_type=None):
val.filter(addon__type=addon_type)
val = list(val[:limit])
- cache.set(key, val, 0)
+ cache.set(key, val, None)
return val
@classmethod
@@ -492,7 +492,7 @@ def get_breakdown(cls, user):
"""
with amo.models.skip_cache():
val = list(ReviewerScore.objects.raw(sql, [user.id]))
- cache.set(key, val, 0)
+ cache.set(key, val, None)
return val
@classmethod
@@ -605,7 +605,7 @@ def get_leaderboards(cls, user, days=7, types=None, addon_type=None):
'leader_near': leader_near,
'user_rank': user_rank,
}
- cache.set(key, val, 0)
+ cache.set(key, val, None)
return val
@classmethod
diff --git a/apps/editors/tests/test_views_themes.py b/apps/editors/tests/test_views_themes.py
index ed7e2f3e484..9b55ac210f3 100644
--- a/apps/editors/tests/test_views_themes.py
+++ b/apps/editors/tests/test_views_themes.py
@@ -178,10 +178,10 @@ def test_commit(self, copy_mock, create_preview_mock,
eq_(themes[4].addon.reload().current_version.version,
str(float(old_version) + 1))
else:
- eq_(themes[0].addon.status, amo.STATUS_REVIEW_PENDING)
- eq_(themes[1].addon.status, amo.STATUS_REVIEW_PENDING)
- eq_(themes[2].addon.status, amo.STATUS_REJECTED)
- eq_(themes[3].addon.status, amo.STATUS_REJECTED)
+ eq_(themes[0].addon.reload().status, amo.STATUS_REVIEW_PENDING)
+ eq_(themes[1].addon.reload().status, amo.STATUS_REVIEW_PENDING)
+ eq_(themes[2].addon.reload().status, amo.STATUS_REJECTED)
+ eq_(themes[3].addon.reload().status, amo.STATUS_REJECTED)
eq_(themes[4].addon.reload().status, amo.STATUS_PUBLIC)
eq_(ActivityLog.objects.count(), 4 if self.rereview else 5)
diff --git a/apps/files/tests/test_views.py b/apps/files/tests/test_views.py
index da7f8461215..b7c43eaa9a5 100644
--- a/apps/files/tests/test_views.py
+++ b/apps/files/tests/test_views.py
@@ -381,7 +381,7 @@ def test_directory(self):
def test_unicode(self):
self.file_viewer.src = unicode_filenames
self.file_viewer.extract()
- res = self.client.get(self.file_url(iri_to_uri(u'\u1109\u1161\u11a9')))
+ res = self.client.get(self.file_url(u'\u1109\u1161\u11a9'))
eq_(res.status_code, 200)
def test_serve_no_token(self):
diff --git a/apps/search/tests/test_views.py b/apps/search/tests/test_views.py
index 5546f350079..fd0359348ca 100644
--- a/apps/search/tests/test_views.py
+++ b/apps/search/tests/test_views.py
@@ -128,7 +128,7 @@ def check_heading(self):
class TestESSearch(SearchBase):
- fixtures = ['base/apps', 'base/category', 'tags/tags']
+ fixtures = ['base/apps', 'base/category']
@classmethod
def setUpClass(cls):
@@ -1077,6 +1077,7 @@ def search_addons(self, url, params, addons=[], types=amo.ADDON_TYPES,
eq_(len(data), len(addons))
for got, expected in zip(data, addons):
+ expected.reload()
eq_(int(got['id']), expected.id)
eq_(got['name'], unicode(expected.name))
expected_url = expected.get_url_path()
diff --git a/apps/stats/db.py b/apps/stats/db.py
index 82cd9dc98a4..445c16f72ce 100644
--- a/apps/stats/db.py
+++ b/apps/stats/db.py
@@ -1,10 +1,7 @@
from django.db import models
import phpserialize as php
-try:
- import simplejson as json
-except ImportError:
- import json
+import json
class StatsDictField(models.TextField):
diff --git a/apps/stats/views.py b/apps/stats/views.py
index 8b1de0b7930..8355a667310 100644
--- a/apps/stats/views.py
+++ b/apps/stats/views.py
@@ -1,6 +1,7 @@
import cStringIO
import csv
import itertools
+import json
import logging
import time
from datetime import date, timedelta
@@ -13,7 +14,6 @@
from django.db import connection
from django.db.models import Avg, Count, Q, Sum
from django.shortcuts import get_object_or_404, render
-from django.utils import simplejson
from django.utils.cache import add_never_cache_headers, patch_cache_control
from django.utils.datastructures import SortedDict
@@ -712,5 +712,5 @@ def render_json(request, addon, stats):
# Django's encoder supports date and datetime.
fudge_headers(response, stats)
- simplejson.dump(stats, response, cls=DjangoJSONEncoder)
+ json.dump(stats, response, cls=DjangoJSONEncoder)
return response
diff --git a/apps/translations/fields.py b/apps/translations/fields.py
index 10ea34506c8..475ec8323d4 100644
--- a/apps/translations/fields.py
+++ b/apps/translations/fields.py
@@ -242,7 +242,11 @@ def _has_changed(self, initial, data):
class LocaleValidationError(forms.ValidationError):
def __init__(self, messages, code=None, params=None):
- self.messages = messages
+ self.msgs = messages
+
+ @property
+ def messages(self):
+ return self.msgs
class TransField(_TransField, forms.CharField):
diff --git a/apps/translations/query.py b/apps/translations/query.py
index 3d0126f5863..50dd33dba96 100644
--- a/apps/translations/query.py
+++ b/apps/translations/query.py
@@ -2,7 +2,6 @@
from django.conf import settings
from django.db import models
-from django.db.models.sql import compiler
from django.utils import translation as translation_utils
@@ -27,20 +26,23 @@ def order_by_translation(qs, fieldname):
model = qs.model
field = model._meta.get_field(fieldname)
- # (lhs, rhs, lhs_col, rhs_col) => lhs.lhs_col = rhs.rhs_col
+ # connection is a tuple (lhs, table, join_cols)
connection = (model._meta.db_table, field.rel.to._meta.db_table,
- field.column, field.rel.field_name)
+ field.rel.field_name)
# Doing the manual joins is flying under Django's radar, so we need to make
# sure the initial alias (the main table) is set up.
if not qs.query.tables:
qs.query.get_initial_alias()
- # Force two LEFT JOINs against the translation table. We'll hook up the
+ # Force two new (reuse is an empty set) LEFT OUTER JOINs against the
+ # translation table, without reusing any aliases. We'll hook up the
# language fallbacks later.
qs.query = qs.query.clone(TranslationQuery)
- t1 = qs.query.join(connection, always_create=True, promote=True)
- t2 = qs.query.join(connection, always_create=True, promote=True)
+ t1 = qs.query.join(connection, join_field=field,
+ outer_if_first=True, reuse=set())
+ t2 = qs.query.join(connection, join_field=field,
+ outer_if_first=True, reuse=set())
qs.query.translation_aliases = {field: (t1, t2)}
f1, f2 = '%s.`localized_string`' % t1, '%s.`localized_string`' % t2
@@ -108,8 +110,11 @@ def join_with_locale(self, alias, fallback=None):
qn = self.quote_name_unless_alias
qn2 = self.connection.ops.quote_name
mapping = self.query.alias_map[alias]
- name, alias, join_type, lhs, lhs_col, col, nullable = mapping
- alias_str = (alias != name and ' %s' % alias or '')
+ # name, alias, join_type, lhs, lhs_col, col, nullable = mapping
+ name, alias, join_type, lhs, join_cols, _, join_field = mapping
+ lhs_col = join_field.column
+ rhs_col = join_cols
+ alias_str = '' if alias == name else (' %s' % alias)
if isinstance(fallback, models.Field):
fallback_str = '%s.%s' % (qn(self.query.model._meta.db_table),
@@ -119,5 +124,5 @@ def join_with_locale(self, alias, fallback=None):
return ('%s %s%s ON (%s.%s = %s.%s AND %s.%s = %s)' %
(join_type, qn(name), alias_str,
- qn(lhs), qn2(lhs_col), qn(alias), qn2(col),
+ qn(lhs), qn2(lhs_col), qn(alias), qn2(rhs_col),
qn(alias), qn('locale'), fallback_str))
diff --git a/apps/users/models.py b/apps/users/models.py
index 47e89ef664f..607b799b80a 100644
--- a/apps/users/models.py
+++ b/apps/users/models.py
@@ -313,7 +313,11 @@ def last_login(self):
@amo.cached_property
def reviews(self):
"""All reviews that are not dev replies."""
- return self._reviews_all.filter(reply_to=None)
+ qs = self._reviews_all.filter(reply_to=None)
+ # Force the query to occur immediately. Several
+ # reviews-related tests hang if this isn't done.
+ list(qs)
+ return qs
def anonymize(self):
log.info(u"User (%s: <%s>) is being anonymized." % (self, self.email))
diff --git a/apps/users/templates/users/edit.html b/apps/users/templates/users/edit.html
index 7ab56ad0d0c..29006a29943 100644
--- a/apps/users/templates/users/edit.html
+++ b/apps/users/templates/users/edit.html
@@ -55,7 +55,7 @@