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 56157276b60..16489e1dcb7 100644
--- a/apps/addons/models.py
+++ b/apps/addons/models.py
@@ -1354,7 +1354,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
@@ -1714,7 +1714,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 e9fea169252..b316b5ddff1 100644
--- a/apps/addons/tests/test_models.py
+++ b/apps/addons/tests/test_models.py
@@ -2198,29 +2198,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 66044c85dc9..7a736bd1e09 100644
--- a/apps/amo/tests/test_helpers.py
+++ b/apps/amo/tests/test_helpers.py
@@ -436,5 +436,5 @@ 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/api/tests/test_views.py b/apps/api/tests/test_views.py
index 6f618bb4785..8451cf4eccf 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/devhub/forms.py b/apps/devhub/forms.py
index 150695bf8fa..b4ff51a6621 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):
@@ -896,7 +900,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/tests/test_views_themes.py b/apps/editors/tests/test_views_themes.py
index 0738a01b835..c2f541eb924 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/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 780031ff5be..01270585a86 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
-from django.utils import simplejson
from django.utils.cache import add_never_cache_headers, patch_cache_control
from django.utils.datastructures import SortedDict
@@ -691,5 +691,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 faa09d103f6..1c5d36faa2e 100644
--- a/apps/users/models.py
+++ b/apps/users/models.py
@@ -312,7 +312,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 4745bafb13f..d63b081d7d7 100644
--- a/apps/users/templates/users/edit.html
+++ b/apps/users/templates/users/edit.html
@@ -51,7 +51,7 @@