Implement Django 1.8 compatibility.
* Get rid of addons.query - this will need some benchmarking but it was super ugly so I removed it * Make sure email fields are using 75 char maxlength (original default) * Add replacement for shortcuts.render * various stuff along the way... * Fix zadmin render helper * Less django.shortcuts:render, more our own for compat reasons. * Fix dynamic endpoints compat * Fix render support for blocklist * Save translated fields before we actually save the instance. This avoids getting caught by django's sanity check that avoids an instance being saved with unsaved related fields set. * Fix fix_let_scope_bustage management command * Fix another horribly wrong select_related statement * use django UUIDField where possible * Fix password reset email rendering * Fix slugify to work with translation objects * Fix stats csv test with updated cache-control header * Accept both, verbose and hex version of rss keys * Add migration to normalize devhub rss keys * Support both formats with and without microseconds for handle_date * Copy over render_to_string from jingo and fix it. * Unify usage of smart/force text/bytes, also to make sure 'Translation' objects are passed through properly. * Make translation model json renderable * Unify JSONEncoder usage
This commit is contained in:
Родитель
f2012f52ae
Коммит
7c560174c6
|
@ -3,7 +3,6 @@
|
|||
|
||||
## Forked.
|
||||
-e git+https://github.com/EnTeQuAk/django-cache-machine@1cac98a3e1dcc3cb654665cc67b6b15ab60d62c4#egg=django-cache-machine
|
||||
-e git+https://github.com/andymckay/django-uuidfield.git@029dd1263794ec36c327617cd6c2346da81c8c33#egg=django-uuidfield
|
||||
|
||||
## Out of date or not sure on pypi.
|
||||
-e git+https://github.com/jbalogh/django-mobility.git@e2b60a1f96e4c4aed736395c01bf707e969d8e83#egg=django-mobility
|
||||
|
|
|
@ -501,7 +501,7 @@ class TestRegisterUser(TestCase):
|
|||
assert not user.has_usable_password()
|
||||
|
||||
|
||||
@override_settings(FXA_CONFIG={'default': FXA_CONFIG})
|
||||
@override_settings(FXA_CONFIG={'default': FXA_CONFIG, 'internal': FXA_CONFIG})
|
||||
class BaseAuthenticationView(APITestCase, InitializeSessionMixin):
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import (
|
||||
ugettext as _, ugettext_lazy as _lazy, pgettext_lazy)
|
||||
|
||||
|
@ -9,6 +8,7 @@ import jinja2
|
|||
from olympia import amo
|
||||
from olympia.amo.helpers import urlparams
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import render
|
||||
|
||||
|
||||
@jinja2.contextfunction
|
||||
|
|
|
@ -5,6 +5,7 @@ from olympia.files.tasks import fix_let_scope_bustage_in_addons
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
use_argparse = False
|
||||
args = '<addon_id addon_id ...>'
|
||||
help = """Fix the "let scope bustage" (bug 1224686) for a list of add-ons.
|
||||
Only the last version of each add-on will be fixed, and its version bumped."""
|
||||
|
|
|
@ -32,9 +32,9 @@ from olympia.addons.utils import (
|
|||
from olympia.amo import helpers
|
||||
from olympia.amo.decorators import use_master, write
|
||||
from olympia.amo.utils import (
|
||||
attach_trans_dict, cache_ns_key, chunked, JSONEncoder,
|
||||
attach_trans_dict, cache_ns_key, chunked,
|
||||
no_translation, send_mail, slugify, sorted_groupby, timer, to_language,
|
||||
urlparams, find_language)
|
||||
urlparams, find_language, AMOJSONEncoder)
|
||||
from olympia.amo.urlresolvers import get_outgoing_url, reverse
|
||||
from olympia.constants.categories import CATEGORIES, CATEGORIES_BY_ID
|
||||
from olympia.files.models import File
|
||||
|
@ -48,7 +48,7 @@ from olympia.users.models import UserForeignKey, UserProfile
|
|||
from olympia.versions.compare import version_int
|
||||
from olympia.versions.models import inherit_nomination, Version
|
||||
|
||||
from . import query, signals
|
||||
from . import signals
|
||||
|
||||
|
||||
log = commonware.log.getLogger('z.addons')
|
||||
|
@ -141,7 +141,6 @@ class AddonManager(ManagerBase):
|
|||
|
||||
def get_queryset(self):
|
||||
qs = super(AddonManager, self).get_queryset()
|
||||
qs = qs._clone(klass=query.IndexQuerySet)
|
||||
if not self.include_deleted:
|
||||
qs = qs.exclude(status=amo.STATUS_DELETED)
|
||||
if not self.include_unlisted:
|
||||
|
@ -1747,7 +1746,7 @@ class Persona(caching.CachingMixin, models.Model):
|
|||
def json_data(self):
|
||||
"""Persona JSON Data for Browser/extension preview."""
|
||||
return json.dumps(self.theme_data,
|
||||
separators=(',', ':'), cls=JSONEncoder)
|
||||
separators=(',', ':'), cls=AMOJSONEncoder)
|
||||
|
||||
def authors_other_addons(self, app=None):
|
||||
"""
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
from django.db import models
|
||||
from django.db.models.sql import compiler
|
||||
|
||||
import caching.base as caching
|
||||
|
||||
|
||||
class IndexQuerySet(caching.CachingQuerySet):
|
||||
|
||||
def with_index(self, **kw):
|
||||
"""
|
||||
Suggest indexes that should be used with this query as key-value pairs.
|
||||
|
||||
qs.with_index(t1='xxx') => INNER JOIN t1 USE INDEX (`xxx`)
|
||||
"""
|
||||
q = self._clone()
|
||||
if not isinstance(q.query, IndexQuery):
|
||||
q.query = self.query.clone(IndexQuery)
|
||||
q.query.index_map.update(kw)
|
||||
return q
|
||||
|
||||
def fetch_missed(self, pks):
|
||||
# Remove the indexes before doing the id query.
|
||||
if hasattr(self.query, 'index_map'):
|
||||
index_map = self.query.index_map
|
||||
self.query.index_map = {}
|
||||
rv = super(IndexQuerySet, self).fetch_missed(pks)
|
||||
self.query.index_map = index_map
|
||||
return rv
|
||||
else:
|
||||
return super(IndexQuerySet, self).fetch_missed(pks)
|
||||
|
||||
|
||||
class IndexQuery(models.query.sql.Query):
|
||||
"""
|
||||
Extends sql.Query to make it possible to specify indexes to use.
|
||||
"""
|
||||
|
||||
def clone(self, klass=None, **kwargs):
|
||||
# Maintain index_map across clones.
|
||||
c = super(IndexQuery, self).clone(klass, **kwargs)
|
||||
c.index_map = dict(self.index_map)
|
||||
return c
|
||||
|
||||
def get_compiler(self, using=None, connection=None):
|
||||
# Call super to figure out using and connection.
|
||||
c = super(IndexQuery, self).get_compiler(using, connection)
|
||||
return IndexCompiler(self, c.connection, c.using)
|
||||
|
||||
def _setup_query(self):
|
||||
if not hasattr(self, 'index_map'):
|
||||
self.index_map = {}
|
||||
|
||||
def get_count(self, using):
|
||||
# Don't use the index for counts, it's slower.
|
||||
index_map = self.index_map
|
||||
self.index_map = {}
|
||||
count = super(IndexQuery, self).get_count(using)
|
||||
self.index_map = index_map
|
||||
return count
|
||||
|
||||
|
||||
class IndexCompiler(compiler.SQLCompiler):
|
||||
|
||||
def get_from_clause(self):
|
||||
"""
|
||||
Returns a list of strings that are joined together to go after the
|
||||
"FROM" part of the query, as well as a list any extra parameters that
|
||||
need to be included. Sub-classes, can override this to create a
|
||||
from-clause via a "select".
|
||||
|
||||
This should only be called after any SQL construction methods that
|
||||
might change the tables we need. This means the select columns and
|
||||
ordering must be done first.
|
||||
"""
|
||||
result = []
|
||||
qn = self.quote_name_unless_alias
|
||||
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, 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.
|
||||
continue
|
||||
alias_str = (alias != name and ' %s' % alias or '')
|
||||
# jbalogh wuz here. #
|
||||
if name in index_map:
|
||||
use_index = 'USE INDEX (%s)' % qn(index_map[name])
|
||||
else:
|
||||
use_index = ''
|
||||
if join_type and not first:
|
||||
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 = connector = '' if first else ', '
|
||||
result.append('%s%s%s %s' % (connector, qn(name), alias_str,
|
||||
use_index))
|
||||
# jbalogh out. #
|
||||
first = False
|
||||
for t in self.query.extra_tables:
|
||||
alias, unused = self.query.table_alias(t)
|
||||
# Only add the alias if it's not already present (the table_alias()
|
||||
# calls increments the refcount, so an alias refcount of one means
|
||||
# this is the only reference.
|
||||
if (alias not in self.query.alias_map or
|
||||
self.query.alias_refcount[alias] == 1):
|
||||
connector = not first and ', ' or ''
|
||||
result.append('%s%s' % (connector, qn(alias)))
|
||||
first = False
|
||||
return result, from_params
|
|
@ -9,8 +9,7 @@ from operator import attrgetter
|
|||
from django import http
|
||||
from django.conf import settings
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import (
|
||||
get_list_or_404, get_object_or_404, redirect, render)
|
||||
from django.shortcuts import get_list_or_404, get_object_or_404, redirect
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
@ -36,7 +35,7 @@ from olympia import amo
|
|||
from olympia.amo import messages
|
||||
from olympia.amo.decorators import post_required
|
||||
from olympia.amo.forms import AbuseForm
|
||||
from olympia.amo.utils import randslice
|
||||
from olympia.amo.utils import randslice, render
|
||||
from olympia.amo.models import manual_order
|
||||
from olympia.amo import urlresolvers
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
|
@ -291,27 +290,22 @@ class BaseFilter(object):
|
|||
return self.base_queryset.top_paid(listed=False)
|
||||
|
||||
def filter_popular(self):
|
||||
return (self.base_queryset.order_by('-weekly_downloads')
|
||||
.with_index(addons='downloads_type_idx'))
|
||||
return self.base_queryset.order_by('-weekly_downloads')
|
||||
|
||||
def filter_downloads(self):
|
||||
return self.filter_popular()
|
||||
|
||||
def filter_users(self):
|
||||
return (self.base_queryset.order_by('-average_daily_users')
|
||||
.with_index(addons='adus_type_idx'))
|
||||
return self.base_queryset.order_by('-average_daily_users')
|
||||
|
||||
def filter_created(self):
|
||||
return (self.base_queryset.order_by('-created')
|
||||
.with_index(addons='created_type_idx'))
|
||||
return self.base_queryset.order_by('-created')
|
||||
|
||||
def filter_updated(self):
|
||||
return (self.base_queryset.order_by('-last_updated')
|
||||
.with_index(addons='last_updated_type_idx'))
|
||||
return self.base_queryset.order_by('-last_updated')
|
||||
|
||||
def filter_rating(self):
|
||||
return (self.base_queryset.order_by('-bayesian_rating')
|
||||
.with_index(addons='rating_type_idx'))
|
||||
return self.base_queryset.order_by('-bayesian_rating')
|
||||
|
||||
def filter_hotness(self):
|
||||
return self.base_queryset.order_by('-hotness')
|
||||
|
|
|
@ -10,11 +10,11 @@ from django.db import connection, transaction
|
|||
import commonware.log
|
||||
|
||||
from olympia.amo import get_user, set_user
|
||||
from olympia.accounts.utils import redirect_for_login
|
||||
from olympia.users.utils import get_task_user
|
||||
|
||||
from . import models as context
|
||||
from .utils import JSONEncoder
|
||||
from olympia.accounts.utils import redirect_for_login
|
||||
from .utils import AMOJSONEncoder
|
||||
|
||||
|
||||
task_log = commonware.log.getLogger('z.task')
|
||||
|
@ -107,7 +107,7 @@ def json_response(response, has_trans=False, status_code=200):
|
|||
then use the json_view decorator.
|
||||
"""
|
||||
if has_trans:
|
||||
response = json.dumps(response, cls=JSONEncoder)
|
||||
response = json.dumps(response, cls=AMOJSONEncoder)
|
||||
else:
|
||||
response = json.dumps(response)
|
||||
return http.HttpResponse(response,
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
from django.forms import CheckboxInput
|
||||
from django.utils.translation import (
|
||||
ugettext as _, trim_whitespace, to_locale, get_language)
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import format_html
|
||||
from django.template import defaultfilters
|
||||
|
||||
|
@ -232,7 +232,7 @@ def page_name(app=None):
|
|||
@register.function
|
||||
@jinja2.contextfunction
|
||||
def page_title(context, title):
|
||||
title = smart_unicode(title)
|
||||
title = force_text(title)
|
||||
base_title = page_name(context['request'].APP)
|
||||
# The following line doesn't use string formatting because we want to
|
||||
# preserve the type of `title` in case it's a jinja2 `Markup` (safe,
|
||||
|
@ -326,7 +326,7 @@ def strip_html(s, just_kidding=False):
|
|||
if not s:
|
||||
return ''
|
||||
else:
|
||||
s = re.sub(r'<.*?>', '', smart_unicode(s, errors='ignore'))
|
||||
s = re.sub(r'<.*?>', '', force_text(s, errors='ignore'))
|
||||
return re.sub(r'<.*?>', '', s)
|
||||
|
||||
|
||||
|
@ -587,7 +587,7 @@ def inline_css(bundle, media=False, debug=None):
|
|||
Added: turns relative links to absolute ones using STATIC_URL.
|
||||
"""
|
||||
if debug is None:
|
||||
debug = getattr(settings, 'TEMPLATE_DEBUG', False)
|
||||
debug = getattr(settings, 'DEBUG', False)
|
||||
|
||||
if debug:
|
||||
items = [_get_compiled_css_url(i)
|
||||
|
|
|
@ -14,14 +14,14 @@ from django.contrib.sessions.middleware import SessionMiddleware
|
|||
from django.core.urlresolvers import is_valid_path
|
||||
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
|
||||
from django.middleware import common
|
||||
from django.shortcuts import render
|
||||
from django.utils.cache import patch_vary_headers, patch_cache_control
|
||||
from django.utils.encoding import iri_to_uri, smart_str
|
||||
from django.utils.encoding import iri_to_uri, force_bytes
|
||||
from django.utils.translation import activate
|
||||
|
||||
import MySQLdb as mysql
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.utils import render
|
||||
from . import urlresolvers
|
||||
from .helpers import urlparams
|
||||
|
||||
|
@ -58,7 +58,7 @@ class LocaleAndAppURLMiddleware(object):
|
|||
# from query params so we don't have an infinite loop.
|
||||
prefixer.locale = ''
|
||||
new_path = prefixer.fix(prefixer.shortened_path)
|
||||
query = dict((smart_str(k), request.GET[k]) for k in request.GET)
|
||||
query = dict((force_bytes(k), request.GET[k]) for k in request.GET)
|
||||
query.pop('lang')
|
||||
return redirect_type(urlparams(new_path, **query))
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ import threading
|
|||
|
||||
from django.conf import settings
|
||||
from django.db import models, transaction
|
||||
from django.utils import encoding, translation
|
||||
from django.utils import translation
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
import caching.base
|
||||
import elasticsearch
|
||||
|
@ -11,6 +12,7 @@ import multidb.pinning
|
|||
from django_statsd.clients import statsd
|
||||
|
||||
import olympia.lib.queryset_transform as queryset_transform
|
||||
from olympia.translations.hold import save_translations
|
||||
|
||||
from . import search
|
||||
|
||||
|
@ -41,31 +43,6 @@ def skip_cache():
|
|||
_locals.skip_cache = old
|
||||
|
||||
|
||||
# This is sadly a copy and paste of annotate to get around this
|
||||
# ticket http://code.djangoproject.com/ticket/14707
|
||||
def annotate(self, *args, **kwargs):
|
||||
|
||||
for arg in args:
|
||||
if arg.default_alias in kwargs:
|
||||
raise ValueError("The %s named annotation conflicts with the "
|
||||
"default name for another annotation."
|
||||
% arg.default_alias)
|
||||
kwargs[arg.default_alias] = arg
|
||||
|
||||
obj = self._clone()
|
||||
|
||||
obj._setup_aggregate_query(kwargs.keys())
|
||||
|
||||
# Add the aggregates to the query
|
||||
for (alias, aggregate_expr) in kwargs.items():
|
||||
obj.query.add_aggregate(
|
||||
aggregate_expr, self.model, alias, is_summary=False)
|
||||
|
||||
return obj
|
||||
|
||||
models.query.QuerySet.annotate = annotate
|
||||
|
||||
|
||||
class TransformQuerySet(queryset_transform.TransformQuerySet):
|
||||
|
||||
def pop_transforms(self):
|
||||
|
@ -367,7 +344,7 @@ class ModelBase(SearchMixin, caching.base.CachingMixin, models.Model):
|
|||
need it and it avoids invalidation bugs with FETCH_BY_ID.
|
||||
"""
|
||||
key_parts = ('o', cls._meta, pk, 'default')
|
||||
return ':'.join(map(encoding.smart_unicode, key_parts))
|
||||
return ':'.join(map(force_text, key_parts))
|
||||
|
||||
def reload(self):
|
||||
"""Reloads the instance from the database."""
|
||||
|
@ -414,6 +391,15 @@ class ModelBase(SearchMixin, caching.base.CachingMixin, models.Model):
|
|||
models.signals.post_save.send(sender=cls, instance=self,
|
||||
created=False)
|
||||
|
||||
def save(self, **kwargs):
|
||||
# Unfortunately we have to save our translations before we call `save`
|
||||
# since Django verifies m2n relations with unsaved parent relations
|
||||
# and throws an error.
|
||||
# https://docs.djangoproject.com/en/1.9/topics/db/examples/one_to_one/
|
||||
if hasattr(self._meta, 'translated_fields'):
|
||||
save_translations(id(self))
|
||||
return super(ModelBase, self).save(**kwargs)
|
||||
|
||||
|
||||
def manual_order(qs, pks, pk_name='id'):
|
||||
"""
|
||||
|
|
|
@ -7,7 +7,7 @@ require leading directories to exist. The default Django file system storage
|
|||
"""
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
DEFAULT_CHUNK_SIZE = 64 * 2 ** 10 # 64kB
|
||||
|
||||
|
@ -34,8 +34,8 @@ def walk_storage(path, topdown=True, onerror=None, followlinks=False,
|
|||
new_roots = []
|
||||
for root in roots:
|
||||
dirs, files = storage.listdir(root)
|
||||
files = [smart_str(f) for f in files]
|
||||
dirs = [smart_str(d) for d in dirs]
|
||||
files = [force_bytes(f) for f in files]
|
||||
dirs = [force_bytes(d) for d in dirs]
|
||||
yield root, dirs, files
|
||||
for dn in dirs:
|
||||
new_roots.append('%s/%s' % (root, dn))
|
||||
|
|
|
@ -55,7 +55,7 @@ from olympia.users.models import UserProfile
|
|||
from . import dynamic_urls
|
||||
|
||||
|
||||
# We might now have gettext available in jinja2.env.globals when running tests.
|
||||
# We might not have gettext available in jinja2.env.globals when running tests.
|
||||
# It's only added to the globals when activating a language (which
|
||||
# is usually done in the middlewares). During tests, however, we might not be
|
||||
# running middlewares, and thus not activating a language, and thus not
|
||||
|
@ -573,7 +573,10 @@ def _get_created(created):
|
|||
random.randint(0, 59)) # Seconds
|
||||
|
||||
|
||||
def addon_factory(status=amo.STATUS_PUBLIC, version_kw={}, file_kw={}, **kw):
|
||||
def addon_factory(
|
||||
status=amo.STATUS_PUBLIC, version_kw=None, file_kw=None, **kw):
|
||||
version_kw = version_kw or {}
|
||||
|
||||
# Disconnect signals until the last save.
|
||||
post_save.disconnect(addon_update_search_index, sender=Addon,
|
||||
dispatch_uid='addons.search.index')
|
||||
|
@ -606,32 +609,38 @@ def addon_factory(status=amo.STATUS_PUBLIC, version_kw={}, file_kw={}, **kw):
|
|||
# Save 1.
|
||||
if type_ == amo.ADDON_PERSONA:
|
||||
# Personas need to start life as an extension for versioning.
|
||||
a = Addon.objects.create(type=amo.ADDON_EXTENSION, **kwargs)
|
||||
addon = Addon.objects.create(type=amo.ADDON_EXTENSION, **kwargs)
|
||||
else:
|
||||
a = Addon.objects.create(type=type_, **kwargs)
|
||||
version = version_factory(file_kw, addon=a, **version_kw) # Save 2.
|
||||
a.update_version()
|
||||
a.status = status
|
||||
addon = Addon.objects.create(type=type_, **kwargs)
|
||||
|
||||
# Save 2.
|
||||
version = version_factory(file_kw, addon=addon, **version_kw)
|
||||
addon.update_version()
|
||||
addon.status = status
|
||||
if type_ == amo.ADDON_PERSONA:
|
||||
a.type = type_
|
||||
persona_id = persona_id if persona_id is not None else a.id
|
||||
Persona.objects.create(addon=a, popularity=a.weekly_downloads,
|
||||
persona_id=persona_id) # Save 3.
|
||||
addon.type = type_
|
||||
persona_id = persona_id if persona_id is not None else addon.id
|
||||
|
||||
# Save 3.
|
||||
Persona.objects.create(
|
||||
addon=addon, popularity=addon.weekly_downloads,
|
||||
persona_id=persona_id)
|
||||
|
||||
for tag in tags:
|
||||
Tag(tag_text=tag).save_tag(a)
|
||||
Tag(tag_text=tag).save_tag(addon)
|
||||
|
||||
# Put signals back.
|
||||
post_save.connect(addon_update_search_index, sender=Addon,
|
||||
dispatch_uid='addons.search.index')
|
||||
|
||||
a.save() # Save 4.
|
||||
# Save 4.
|
||||
addon.save()
|
||||
|
||||
if 'nomination' in version_kw:
|
||||
# If a nomination date was set on the version, then it might have been
|
||||
# erased at post_save by addons.models.watch_status()
|
||||
version.save()
|
||||
return a
|
||||
return addon
|
||||
|
||||
|
||||
def collection_factory(**kw):
|
||||
|
@ -700,7 +709,7 @@ def user_factory(**kw):
|
|||
return user
|
||||
|
||||
|
||||
def version_factory(file_kw={}, **kw):
|
||||
def version_factory(file_kw=None, **kw):
|
||||
# We can't create duplicates of AppVersions, so make sure the versions are
|
||||
# not already created in fixtures (use fake versions).
|
||||
min_app_version = kw.pop('min_app_version', '4.0.99')
|
||||
|
@ -718,6 +727,7 @@ def version_factory(file_kw={}, **kw):
|
|||
ApplicationsVersions.objects.get_or_create(application=application,
|
||||
version=v, min=av_min,
|
||||
max=av_max)
|
||||
file_kw = file_kw or {}
|
||||
file_factory(version=v, **file_kw)
|
||||
return v
|
||||
|
||||
|
@ -740,8 +750,15 @@ class ESTestCase(TestCase):
|
|||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.es = amo_search.get_es(timeout=settings.ES_TIMEOUT)
|
||||
|
||||
cls._SEARCH_ANALYZER_MAP = amo.SEARCH_ANALYZER_MAP
|
||||
amo.SEARCH_ANALYZER_MAP = {
|
||||
'english': ['en-us'],
|
||||
'spanish': ['es'],
|
||||
}
|
||||
super(ESTestCase, cls).setUpClass()
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
try:
|
||||
cls.es.cluster.health()
|
||||
except Exception, e:
|
||||
|
@ -751,11 +768,6 @@ class ESTestCase(TestCase):
|
|||
list(e.args[1:]))
|
||||
raise
|
||||
|
||||
cls._SEARCH_ANALYZER_MAP = amo.SEARCH_ANALYZER_MAP
|
||||
amo.SEARCH_ANALYZER_MAP = {
|
||||
'english': ['en-us'],
|
||||
'spanish': ['es'],
|
||||
}
|
||||
aliases_and_indexes = set(settings.ES_INDEXES.values() +
|
||||
cls.es.indices.get_aliases().keys())
|
||||
for key in aliases_and_indexes:
|
||||
|
@ -778,6 +790,7 @@ class ESTestCase(TestCase):
|
|||
'alias': settings.ES_INDEXES['stats']}}
|
||||
]
|
||||
cls.es.indices.update_aliases({'actions': actions})
|
||||
super(ESTestCase, cls).setUpTestData()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
@ -813,8 +826,8 @@ class ESTestCase(TestCase):
|
|||
class ESTestCaseWithAddons(ESTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUp(cls):
|
||||
super(ESTestCaseWithAddons, cls).setUpClass()
|
||||
def setUpTestData(cls):
|
||||
super(ESTestCaseWithAddons, cls).setUpTestData()
|
||||
# Load the fixture here, to not be overloaded by a child class'
|
||||
# fixture attribute.
|
||||
call_command('loaddata', 'addons/base_es')
|
||||
|
@ -827,7 +840,7 @@ class ESTestCaseWithAddons(ESTestCase):
|
|||
cls.refresh()
|
||||
|
||||
@classmethod
|
||||
def tearDown(cls):
|
||||
def tearDownClass(cls):
|
||||
try:
|
||||
unindex_addons([a.id for a in cls._addons])
|
||||
cls._addons = []
|
||||
|
@ -908,14 +921,13 @@ class WithDynamicEndpoints(TestCase):
|
|||
is_class = False
|
||||
if is_class:
|
||||
view = view.as_view()
|
||||
dynamic_urls.urlpatterns = django_urls.patterns(
|
||||
'',
|
||||
django_urls.url(url_regex, view),
|
||||
)
|
||||
|
||||
dynamic_urls.urlpatterns = [django_urls.url(url_regex, view)]
|
||||
|
||||
self.addCleanup(self._clean_up_dynamic_urls)
|
||||
|
||||
def _clean_up_dynamic_urls(self):
|
||||
dynamic_urls.urlpatterns = None
|
||||
dynamic_urls.urlpatterns = []
|
||||
|
||||
|
||||
def get_temp_filename():
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
# In an effort to make the tests more readable, this is populated at the
|
||||
# start of each test. See amo/tests/__init__.py:WithDynamicEndpoints
|
||||
urlpatterns = None
|
||||
urlpatterns = []
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import jingo
|
||||
import mock
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
import pytest
|
||||
|
||||
from olympia.amo.utils import render
|
||||
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ class HttpHttpsOnlyURLFieldTestCase(TestCase):
|
|||
self.field.clean(u'ftps://foo.com/')
|
||||
|
||||
def test_no_scheme_assumes_http(self):
|
||||
assert self.field.clean(u'foo.com') == 'http://foo.com/'
|
||||
assert self.field.clean(u'foo.com') == 'http://foo.com'
|
||||
|
||||
def test_http_scheme(self):
|
||||
assert self.field.clean(u'http://foo.com/') == u'http://foo.com/'
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.conf import settings
|
|||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.test.client import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import encoding
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
import jingo
|
||||
import pytest
|
||||
|
@ -78,7 +78,7 @@ def test_page_title():
|
|||
request.APP = amo.FIREFOX
|
||||
s = render('{{ page_title(x) }}',
|
||||
{'request': request,
|
||||
'x': encoding.smart_str(u'\u05d0\u05d5\u05e1\u05e3')})
|
||||
'x': force_bytes(u'\u05d0\u05d5\u05e1\u05e3')})
|
||||
|
||||
|
||||
def test_page_title_markup():
|
||||
|
|
|
@ -11,7 +11,7 @@ import jinja2
|
|||
|
||||
from django.conf import settings
|
||||
from django.core import urlresolvers
|
||||
from django.utils import encoding
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.translation.trans_real import parse_accept_lang_header
|
||||
|
||||
from olympia import amo
|
||||
|
@ -185,7 +185,7 @@ def get_outgoing_url(url):
|
|||
url_netloc in settings.REDIRECT_URL_WHITELIST):
|
||||
return url
|
||||
|
||||
url = encoding.smart_str(jinja2.utils.Markup(url).unescape())
|
||||
url = force_bytes(jinja2.utils.Markup(url).unescape())
|
||||
sig = hmac.new(settings.REDIRECT_SECRET_KEY,
|
||||
msg=url, digestmod=hashlib.sha256).hexdigest()
|
||||
# Let '&=' through so query params aren't escaped. We probably shouldn't
|
||||
|
|
|
@ -2,7 +2,6 @@ import calendar
|
|||
import collections
|
||||
import contextlib
|
||||
import datetime
|
||||
import decimal
|
||||
import errno
|
||||
import functools
|
||||
import itertools
|
||||
|
@ -18,19 +17,17 @@ import urllib
|
|||
import urlparse
|
||||
|
||||
import django.core.mail
|
||||
from django import http
|
||||
from django.http import HttpResponse
|
||||
from django.conf import settings
|
||||
from django.core import paginator
|
||||
from django.core.cache import cache
|
||||
from django.core.files.storage import (FileSystemStorage,
|
||||
default_storage as storage)
|
||||
from django.core.serializers import json as json_serializer
|
||||
from django.core.validators import validate_slug, ValidationError
|
||||
from django.forms.fields import Field
|
||||
from django.template import Context, loader
|
||||
from django.utils import translation
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
from django.utils.http import urlunquote
|
||||
|
||||
import bleach
|
||||
|
@ -41,11 +38,11 @@ from babel import Locale
|
|||
from django_statsd.clients import statsd
|
||||
from easy_thumbnails import processors
|
||||
from html5lib.serializer.htmlserializer import HTMLSerializer
|
||||
from jingo import get_env
|
||||
from jingo import get_env, get_standard_processors
|
||||
from PIL import Image
|
||||
from validator import unicodehelper
|
||||
from rest_framework.utils.encoders import JSONEncoder
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo import search
|
||||
from olympia.amo import ADDON_ICON_SIZES
|
||||
from olympia.amo.urlresolvers import linkify_with_outgoing, reverse
|
||||
|
@ -56,6 +53,32 @@ from olympia.users.utils import UnsubscribeCode
|
|||
from . import logger_log as log
|
||||
|
||||
|
||||
def render_to_string(request, template, context=None):
|
||||
"""Render a template into a string.
|
||||
|
||||
This is copied and fixed from jingo.
|
||||
"""
|
||||
def get_context():
|
||||
c = {}
|
||||
for processor in get_standard_processors():
|
||||
c.update(processor(request))
|
||||
|
||||
if context is not None:
|
||||
c.update(context.copy())
|
||||
return c
|
||||
|
||||
# If it's not a Template, it must be a path to be loaded.
|
||||
if not isinstance(template, jinja2.environment.Template):
|
||||
template = get_env().get_template(template)
|
||||
|
||||
return template.render(get_context())
|
||||
|
||||
|
||||
def render(request, template, ctx=None, status=None, content_type=None):
|
||||
rendered = render_to_string(request, template, ctx)
|
||||
return HttpResponse(rendered, status=status, content_type=content_type)
|
||||
|
||||
|
||||
def days_ago(n):
|
||||
return datetime.datetime.now() - datetime.timedelta(days=n)
|
||||
|
||||
|
@ -72,7 +95,7 @@ def urlparams(url_, hash=None, **query):
|
|||
|
||||
# Use dict(parse_qsl) so we don't get lists of values.
|
||||
q = url.query
|
||||
query_dict = dict(urlparse.parse_qsl(smart_str(q))) if q else {}
|
||||
query_dict = dict(urlparse.parse_qsl(force_bytes(q))) if q else {}
|
||||
query_dict.update((k, v) for k, v in query.items())
|
||||
|
||||
query_string = urlencode([(k, urlunquote(v)) for k, v in query_dict.items()
|
||||
|
@ -317,31 +340,6 @@ def send_html_mail_jinja(subject, html_template, text_template, context,
|
|||
return msg
|
||||
|
||||
|
||||
class JSONEncoder(json_serializer.DjangoJSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
from olympia.versions.models import ApplicationsVersions
|
||||
|
||||
unicodable = (Translation, Promise)
|
||||
|
||||
if isinstance(obj, unicodable):
|
||||
return unicode(obj)
|
||||
if isinstance(obj, ApplicationsVersions):
|
||||
return {unicode(amo.APP_IDS[obj.application].pretty): {
|
||||
'min': unicode(obj.min), 'max': unicode(obj.max)}}
|
||||
|
||||
return super(JSONEncoder, self).default(obj)
|
||||
|
||||
|
||||
class DecimalJSONEncoder(json_serializer.DjangoJSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
if isinstance(obj, decimal.Decimal):
|
||||
return float(obj)
|
||||
|
||||
return super(DecimalJSONEncoder, self).default(obj)
|
||||
|
||||
|
||||
def chunked(seq, n):
|
||||
"""
|
||||
Yield successive n-sized chunks from seq.
|
||||
|
@ -365,7 +363,7 @@ def urlencode(items):
|
|||
try:
|
||||
return urllib.urlencode(items)
|
||||
except UnicodeEncodeError:
|
||||
return urllib.urlencode([(k, smart_str(v)) for k, v in items])
|
||||
return urllib.urlencode([(k, force_bytes(v)) for k, v in items])
|
||||
|
||||
|
||||
def randslice(qs, limit, exclude=None):
|
||||
|
@ -397,7 +395,8 @@ def slugify(s, ok=SLUG_OK, lower=True, spaces=False, delimiter='-'):
|
|||
# L and N signify letter/number.
|
||||
# http://www.unicode.org/reports/tr44/tr44-4.html#GC_Values_Table
|
||||
rv = []
|
||||
for c in smart_unicode(s):
|
||||
|
||||
for c in force_text(s):
|
||||
cat = unicodedata.category(c)[0]
|
||||
if cat in 'LN' or c in ok:
|
||||
rv.append(c)
|
||||
|
@ -591,7 +590,7 @@ def get_locale_from_lang(lang):
|
|||
return Locale.parse(translation.to_locale(lang))
|
||||
|
||||
|
||||
class HttpResponseSendFile(http.HttpResponse):
|
||||
class HttpResponseSendFile(HttpResponse):
|
||||
|
||||
def __init__(self, request, path, content=None, status=None,
|
||||
content_type='application/octet-stream', etag=None):
|
||||
|
@ -689,8 +688,8 @@ class ESPaginator(paginator.Paginator):
|
|||
def smart_path(string):
|
||||
"""Returns a string you can pass to path.path safely."""
|
||||
if os.path.supports_unicode_filenames:
|
||||
return smart_unicode(string)
|
||||
return smart_str(string)
|
||||
return force_text(string)
|
||||
return force_bytes(string)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
@ -714,7 +713,7 @@ def escape_all(v, linkify_only_full=False):
|
|||
|
||||
"""
|
||||
if isinstance(v, basestring):
|
||||
v = jinja2.escape(smart_unicode(v))
|
||||
v = jinja2.escape(force_text(v))
|
||||
v = linkify_with_outgoing(v, only_full=linkify_only_full)
|
||||
return v
|
||||
elif isinstance(v, list):
|
||||
|
@ -724,7 +723,7 @@ def escape_all(v, linkify_only_full=False):
|
|||
for k, lv in v.iteritems():
|
||||
v[k] = escape_all(lv, linkify_only_full=linkify_only_full)
|
||||
elif isinstance(v, Translation):
|
||||
v = jinja2.escape(smart_unicode(v.localized_string))
|
||||
v = jinja2.escape(force_text(v))
|
||||
return v
|
||||
|
||||
|
||||
|
@ -775,8 +774,8 @@ class LocalFileStorage(FileSystemStorage):
|
|||
|
||||
def _smart_path(self, string):
|
||||
if os.path.supports_unicode_filenames:
|
||||
return smart_unicode(string)
|
||||
return smart_str(string)
|
||||
return force_text(string)
|
||||
return force_bytes(string)
|
||||
|
||||
|
||||
def translations_for_field(field):
|
||||
|
@ -928,3 +927,10 @@ def utc_millesecs_from_epoch(for_datetime=None):
|
|||
if not for_datetime:
|
||||
for_datetime = datetime.datetime.now()
|
||||
return calendar.timegm(for_datetime.utctimetuple()) * 1000
|
||||
|
||||
|
||||
class AMOJSONEncoder(JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Translation):
|
||||
return force_text(obj)
|
||||
return super(AMOJSONEncoder, self).default(obj)
|
||||
|
|
|
@ -5,12 +5,12 @@ from django import http
|
|||
from django.conf import settings
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.cache import never_cache
|
||||
|
||||
from django_statsd.clients import statsd
|
||||
|
||||
from olympia import amo, legacy_api
|
||||
from olympia.amo.utils import render
|
||||
|
||||
from . import monitors
|
||||
|
||||
|
|
|
@ -60,6 +60,11 @@ class BaseESSerializer(ModelSerializer):
|
|||
def handle_date(self, value):
|
||||
if not value:
|
||||
return None
|
||||
|
||||
# Don't be picky about microseconds here. We get them some time
|
||||
# so we have to support them. So let's strip microseconds and handle
|
||||
# the datetime in a unified way.
|
||||
value = value.partition('.')[0]
|
||||
return datetime.strptime(value, u'%Y-%m-%dT%H:%M:%S')
|
||||
|
||||
def _attach_fields(self, obj, data, field_names):
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.api.serializers import BaseESSerializer
|
||||
|
||||
|
||||
class BasicSerializer(BaseESSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Addon
|
||||
fields = ()
|
||||
|
||||
|
||||
def test_handle_date_strips_microseconds():
|
||||
serializer = BasicSerializer()
|
||||
date = datetime.utcnow()
|
||||
assert date.microsecond
|
||||
assert (
|
||||
serializer.handle_date(date.isoformat()) ==
|
||||
date.replace(microsecond=0))
|
|
@ -8,6 +8,7 @@ from olympia.applications.models import AppVersion
|
|||
|
||||
|
||||
class Command(BaseCommand):
|
||||
use_argparse = False
|
||||
help = ('Add a new version of an application. Syntax: \n'
|
||||
' ./manage.py addnewversion <application_name> <version>')
|
||||
log = commonware.log.getLogger('z.appversions')
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import render
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
import caching.base as caching
|
||||
|
@ -7,6 +6,7 @@ import caching.base as caching
|
|||
from olympia import amo
|
||||
from olympia.amo.helpers import url, absolutify
|
||||
from olympia.amo.feeds import NonAtomicFeed
|
||||
from olympia.amo.utils import render
|
||||
|
||||
from .models import AppVersion
|
||||
|
||||
|
|
|
@ -90,6 +90,8 @@ class CollectionManager(ManagerBase):
|
|||
class Collection(ModelBase):
|
||||
TYPE_CHOICES = amo.COLLECTION_CHOICES.items()
|
||||
|
||||
# TODO: Use models.UUIDField but it uses max_length=32 hex (no hyphen)
|
||||
# uuids so needs some migration.
|
||||
uuid = models.CharField(max_length=36, blank=True, unique=True)
|
||||
name = TranslatedField(require_locale=False)
|
||||
# nickname is deprecated. Use slug.
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.core.cache import cache
|
|||
from django.forms import ValidationError
|
||||
import django.test
|
||||
from django.utils.datastructures import MultiValueDict
|
||||
from django.utils import encoding
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
import pytest
|
||||
from mock import patch, Mock
|
||||
|
@ -79,11 +79,11 @@ class HappyUnicodeClient(django.test.Client):
|
|||
"""
|
||||
|
||||
def get(self, path_, *args, **kw):
|
||||
path_ = encoding.smart_str(path_)
|
||||
path_ = force_bytes(path_)
|
||||
return super(HappyUnicodeClient, self).get(path_, *args, **kw)
|
||||
|
||||
def post(self, path_, *args, **kw):
|
||||
path_ = encoding.smart_str(path_)
|
||||
path_ = force_bytes(path_)
|
||||
return super(HappyUnicodeClient, self).post(path_, *args, **kw)
|
||||
|
||||
# Add head, put, options, delete if you need them.
|
||||
|
|
|
@ -7,7 +7,7 @@ from django.core.cache import cache
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.views.decorators.http import require_POST
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
from django.utils.translation import ugettext_lazy as _lazy, ugettext as _
|
||||
|
@ -22,7 +22,7 @@ from olympia.amo.decorators import (
|
|||
allow_mine, json_view, login_required, post_required, restricted_content,
|
||||
write)
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import paginate, urlparams
|
||||
from olympia.amo.utils import paginate, urlparams, render
|
||||
from olympia.access import acl
|
||||
from olympia.accounts.utils import redirect_for_login
|
||||
from olympia.addons.models import Addon
|
||||
|
@ -197,8 +197,7 @@ class CollectionAddonFilter(BaseFilter):
|
|||
return order_by_translation(self.base_queryset, 'name')
|
||||
|
||||
def filter_popular(self):
|
||||
return (self.base_queryset.order_by('-weekly_downloads')
|
||||
.with_index(addons='downloads_type_idx'))
|
||||
return self.base_queryset.order_by('-weekly_downloads')
|
||||
|
||||
|
||||
@allow_mine
|
||||
|
|
|
@ -6,6 +6,7 @@ from .models import BlocklistApp, BlocklistPlugin
|
|||
class BlocklistPluginForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BlocklistPlugin
|
||||
fields = '__all__'
|
||||
|
||||
def clean(self):
|
||||
severity = self.cleaned_data.get('severity')
|
||||
|
@ -21,6 +22,7 @@ class BlocklistPluginForm(forms.ModelForm):
|
|||
class BlocklistAppForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BlocklistApp
|
||||
fields = '__all__'
|
||||
|
||||
def clean(self):
|
||||
blthings = [self.cleaned_data.get('blitem'),
|
||||
|
|
|
@ -9,11 +9,11 @@ from django.core.cache import cache
|
|||
from django.db.models import Q, signals as db_signals
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.cache import patch_cache_control
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
from olympia.amo.utils import sorted_groupby
|
||||
from olympia.amo.utils import sorted_groupby, render
|
||||
from olympia.versions.compare import version_int
|
||||
|
||||
from .models import (
|
||||
|
@ -32,7 +32,7 @@ BlItem = collections.namedtuple('BlItem', 'rows os modified block_id prefs')
|
|||
def blocklist(request, apiver, app, appver):
|
||||
key = 'blocklist:%s:%s:%s' % (apiver, app, appver)
|
||||
# Use md5 to make sure the memcached key is clean.
|
||||
key = hashlib.md5(smart_str(key)).hexdigest()
|
||||
key = hashlib.md5(force_bytes(key)).hexdigest()
|
||||
cache.add('blocklist:keyversion', 1)
|
||||
version = cache.get('blocklist:keyversion')
|
||||
response = cache.get(key, version=version)
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.conf import settings
|
|||
from django.db.transaction import non_atomic_requests
|
||||
from django.http import (Http404, HttpResponsePermanentRedirect,
|
||||
HttpResponseRedirect)
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.utils.translation import ugettext_lazy as _lazy
|
||||
|
||||
|
@ -15,6 +15,7 @@ from mobility.decorators import mobile_template
|
|||
from olympia import amo
|
||||
from olympia.amo.models import manual_order
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import render
|
||||
from olympia.addons.models import Addon, AddonCategory, Category, FrozenAddon
|
||||
from olympia.addons.utils import get_featured_ids, get_creatured_ids
|
||||
from olympia.addons.views import BaseFilter, ESBaseFilter
|
||||
|
|
|
@ -4,13 +4,13 @@ import re
|
|||
from django import http
|
||||
from django.db.models import Count
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import redirect, render
|
||||
from django.shortcuts import redirect
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo import utils as amo_utils
|
||||
from olympia.addons.decorators import owner_or_unlisted_reviewer
|
||||
from olympia.amo.utils import render, paginate
|
||||
from olympia.amo.decorators import post_required
|
||||
from olympia.addons.decorators import owner_or_unlisted_reviewer
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.search.utils import floor_version
|
||||
from olympia.versions.compare import version_dict as vdict
|
||||
|
@ -102,7 +102,7 @@ def reporter_detail(request, guid):
|
|||
works_properly = request.GET.get('works_properly')
|
||||
if works_properly:
|
||||
qs = qs.filter(works_properly=works_properly)
|
||||
reports = amo_utils.paginate(request, qs.order_by('-created'), 100)
|
||||
reports = paginate(request, qs.order_by('-created'), 100)
|
||||
|
||||
return render(request, 'compat/reporter_detail.html',
|
||||
dict(reports=reports, works=works,
|
||||
|
|
|
@ -32,7 +32,6 @@ EMAIL_QA_WHITELIST = env.list('EMAIL_QA_WHITELIST')
|
|||
|
||||
ENV = env('ENV')
|
||||
DEBUG = False
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
DEBUG_PROPAGATE_EXCEPTIONS = False
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CRONJOB_LOCK_PREFIX = DOMAIN
|
||||
|
|
|
@ -22,7 +22,6 @@ SEND_REAL_EMAIL = True
|
|||
|
||||
ENV = env('ENV')
|
||||
DEBUG = False
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
DEBUG_PROPAGATE_EXCEPTIONS = False
|
||||
SESSION_COOKIE_SECURE = True
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ EMAIL_QA_WHITELIST = env.list('EMAIL_QA_WHITELIST')
|
|||
|
||||
ENV = env('ENV')
|
||||
DEBUG = False
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
DEBUG_PROPAGATE_EXCEPTIONS = False
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CRONJOB_LOCK_PREFIX = DOMAIN
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import uuid
|
||||
|
||||
from django import http
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.feedgenerator import Rss201rev2Feed as RSS
|
||||
|
@ -13,8 +16,13 @@ class ActivityFeedRSS(Feed):
|
|||
feed_type = RSS
|
||||
|
||||
def get_object(self, request):
|
||||
rsskey = request.GET.get('privaterss')
|
||||
key = get_object_or_404(RssKey, key=rsskey)
|
||||
try:
|
||||
rsskey = request.GET.get('privaterss')
|
||||
rsskey = uuid.UUID(rsskey)
|
||||
except ValueError:
|
||||
raise http.Http404
|
||||
|
||||
key = get_object_or_404(RssKey, key=rsskey.hex)
|
||||
return key
|
||||
|
||||
def items(self, key):
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from copy import copy
|
||||
from datetime import datetime
|
||||
import json
|
||||
import string
|
||||
import uuid
|
||||
from copy import copy
|
||||
from datetime import datetime
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
|
@ -11,7 +12,6 @@ from django.utils.translation import ugettext as _
|
|||
|
||||
import commonware.log
|
||||
import jinja2
|
||||
from uuidfield.fields import UUIDField
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.models import ModelBase, ManagerBase
|
||||
|
@ -25,11 +25,17 @@ from olympia.users.helpers import user_link
|
|||
from olympia.users.models import UserProfile
|
||||
from olympia.versions.models import Version
|
||||
|
||||
|
||||
log = commonware.log.getLogger('devhub')
|
||||
|
||||
|
||||
class RssKey(models.Model):
|
||||
key = UUIDField(db_column='rsskey', auto=True, unique=True)
|
||||
# TODO: Convert to `models.UUIDField` but apparently we have a max_length
|
||||
# of 36 defined in the database and maybe store things with a hyphen
|
||||
# or maybe not...
|
||||
key = models.CharField(
|
||||
db_column='rsskey', max_length=36,
|
||||
default=lambda: uuid.uuid4().hex, unique=True)
|
||||
addon = models.ForeignKey(Addon, null=True, unique=True)
|
||||
user = models.ForeignKey(UserProfile, null=True, unique=True)
|
||||
created = models.DateField(default=datetime.now)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import uuid
|
||||
from urllib import urlencode
|
||||
|
||||
from pyquery import PyQuery as pq
|
||||
|
@ -174,10 +175,22 @@ class TestActivity(HubTest):
|
|||
# This will give us a new RssKey
|
||||
r = self.get_response()
|
||||
key = RssKey.objects.get()
|
||||
|
||||
# Make sure we generate none-verbose uuid key by default.
|
||||
assert '-' not in key.key
|
||||
|
||||
r = self.get_response(privaterss=key.key)
|
||||
assert r['content-type'] == 'application/rss+xml; charset=utf-8'
|
||||
assert '<title>Recent Changes for My Add-ons</title>' in r.content
|
||||
|
||||
def test_rss_accepts_verbose(self):
|
||||
self.log_creates(5)
|
||||
r = self.get_response()
|
||||
key = RssKey.objects.get()
|
||||
r = self.get_response(privaterss=str(uuid.UUID(key.key)))
|
||||
assert r['content-type'] == 'application/rss+xml; charset=utf-8'
|
||||
assert '<title>Recent Changes for My Add-ons</title>' in r.content
|
||||
|
||||
def test_rss_single(self):
|
||||
self.log_creates(5)
|
||||
self.log_creates(13, self.addon2)
|
||||
|
|
|
@ -2257,7 +2257,9 @@ class TestUpload(BaseUploadTest):
|
|||
def test_redirect(self):
|
||||
r = self.post()
|
||||
upload = FileUpload.objects.get()
|
||||
url = reverse('devhub.upload_detail', args=[upload.uuid, 'json'])
|
||||
url = reverse(
|
||||
'devhub.upload_detail',
|
||||
args=[upload.uuid.hex, 'json'])
|
||||
self.assert3xx(r, url)
|
||||
|
||||
@mock.patch('validator.validate.validate')
|
||||
|
@ -2314,14 +2316,14 @@ class TestUploadDetail(BaseUploadTest):
|
|||
|
||||
upload = FileUpload.objects.get()
|
||||
r = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid, 'json']))
|
||||
args=[upload.uuid.hex, 'json']))
|
||||
assert r.status_code == 200
|
||||
data = json.loads(r.content)
|
||||
assert data['validation']['errors'] == 2
|
||||
assert data['url'] == (
|
||||
reverse('devhub.upload_detail', args=[upload.uuid, 'json']))
|
||||
reverse('devhub.upload_detail', args=[upload.uuid.hex, 'json']))
|
||||
assert data['full_report_url'] == (
|
||||
reverse('devhub.upload_detail', args=[upload.uuid]))
|
||||
reverse('devhub.upload_detail', args=[upload.uuid.hex]))
|
||||
assert data['processed_by_addons_linter'] is False
|
||||
# We must have tiers
|
||||
assert len(data['validation']['messages'])
|
||||
|
@ -2333,7 +2335,7 @@ class TestUploadDetail(BaseUploadTest):
|
|||
|
||||
upload = FileUpload.objects.get()
|
||||
r = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid, 'json']))
|
||||
args=[upload.uuid.hex, 'json']))
|
||||
assert r.status_code == 200
|
||||
data = json.loads(r.content)
|
||||
assert data['processed_by_addons_linter'] is True
|
||||
|
@ -2342,14 +2344,18 @@ class TestUploadDetail(BaseUploadTest):
|
|||
self.post()
|
||||
upload = FileUpload.objects.filter().order_by('-created').first()
|
||||
r = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid]))
|
||||
args=[upload.uuid.hex]))
|
||||
assert r.status_code == 200
|
||||
doc = pq(r.content)
|
||||
assert (doc('header h2').text() ==
|
||||
'Validation Results for {0}_animated.png'.format(upload.uuid))
|
||||
expected = 'Validation Results for {0}_animated.png'.format(
|
||||
upload.uuid.hex)
|
||||
assert doc('header h2').text() == expected
|
||||
|
||||
suite = doc('#addon-validator-suite')
|
||||
assert suite.attr('data-validateurl') == (
|
||||
reverse('devhub.standalone_upload_detail', args=[upload.uuid]))
|
||||
expected = reverse(
|
||||
'devhub.standalone_upload_detail',
|
||||
args=[upload.uuid.hex])
|
||||
assert suite.attr('data-validateurl') == expected
|
||||
|
||||
@mock.patch('olympia.devhub.tasks.run_validator')
|
||||
def check_excluded_platforms(self, xpi, platforms, v):
|
||||
|
@ -2357,7 +2363,7 @@ class TestUploadDetail(BaseUploadTest):
|
|||
self.upload_file(xpi)
|
||||
upload = FileUpload.objects.get()
|
||||
r = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid, 'json']))
|
||||
args=[upload.uuid.hex, 'json']))
|
||||
assert r.status_code == 200
|
||||
data = json.loads(r.content)
|
||||
assert sorted(data['platforms_to_exclude']) == sorted(platforms)
|
||||
|
@ -2414,7 +2420,7 @@ class TestUploadDetail(BaseUploadTest):
|
|||
self.upload_file('unopenable.xpi')
|
||||
upload = FileUpload.objects.get()
|
||||
r = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid, 'json']))
|
||||
args=[upload.uuid.hex, 'json']))
|
||||
data = json.loads(r.content)
|
||||
message = [(m['message'], m.get('fatal', False))
|
||||
for m in data['validation']['messages']]
|
||||
|
@ -2428,7 +2434,7 @@ class TestUploadDetail(BaseUploadTest):
|
|||
self.upload_file('../../../files/fixtures/files/experiment.xpi')
|
||||
upload = FileUpload.objects.get()
|
||||
response = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid, 'json']))
|
||||
args=[upload.uuid.hex, 'json']))
|
||||
data = json.loads(response.content)
|
||||
assert data['validation']['messages'] == []
|
||||
|
||||
|
@ -2438,7 +2444,7 @@ class TestUploadDetail(BaseUploadTest):
|
|||
self.upload_file('../../../files/fixtures/files/experiment.xpi')
|
||||
upload = FileUpload.objects.get()
|
||||
response = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid, 'json']))
|
||||
args=[upload.uuid.hex, 'json']))
|
||||
data = json.loads(response.content)
|
||||
assert data['validation']['messages'] == [
|
||||
{u'tier': 1, u'message': u'You cannot submit this type of add-on',
|
||||
|
@ -2542,7 +2548,7 @@ class TestVersionAddFile(UploadTest):
|
|||
a.save()
|
||||
|
||||
def post(self, platform=amo.PLATFORM_MAC, source=None, beta=False):
|
||||
return self.client.post(self.url, dict(upload=self.upload.uuid,
|
||||
return self.client.post(self.url, dict(upload=self.upload.uuid.hex,
|
||||
platform=platform.id,
|
||||
source=source, beta=beta))
|
||||
|
||||
|
@ -2608,7 +2614,7 @@ class TestVersionAddFile(UploadTest):
|
|||
platform = amo.PLATFORM_MAC.id
|
||||
form = {'DELETE': 'checked', 'id': file_id, 'platform': platform}
|
||||
|
||||
data = formset(form, platform=platform, upload=self.upload.uuid,
|
||||
data = formset(form, platform=platform, upload=self.upload.uuid.hex,
|
||||
initial_count=1, prefix='files')
|
||||
|
||||
r = self.client.post(self.edit_url, data)
|
||||
|
@ -2621,7 +2627,7 @@ class TestVersionAddFile(UploadTest):
|
|||
platform = amo.PLATFORM_MAC.id
|
||||
form = {'DELETE': 'checked', 'id': file_id, 'platform': platform}
|
||||
|
||||
data = formset(form, platform=platform, upload=self.upload.uuid,
|
||||
data = formset(form, platform=platform, upload=self.upload.uuid.hex,
|
||||
initial_count=1, prefix='files')
|
||||
data.update(formset(total_count=1, initial_count=1))
|
||||
|
||||
|
@ -2999,7 +3005,7 @@ class AddVersionTest(UploadTest):
|
|||
def post(self, supported_platforms=[amo.PLATFORM_MAC],
|
||||
override_validation=False, expected_status=200, source=None,
|
||||
beta=False, nomination_type=None):
|
||||
d = dict(upload=self.upload.uuid, source=source,
|
||||
d = dict(upload=self.upload.uuid.hex, source=source,
|
||||
supported_platforms=[p.id for p in supported_platforms],
|
||||
admin_override_validation=override_validation, beta=beta)
|
||||
if nomination_type:
|
||||
|
@ -3250,7 +3256,7 @@ class TestAddBetaVersion(AddVersionTest):
|
|||
def post_additional(self, version, platform=amo.PLATFORM_MAC):
|
||||
url = reverse('devhub.versions.add_file',
|
||||
args=[self.addon.slug, version.id])
|
||||
return self.client.post(url, dict(upload=self.upload.uuid,
|
||||
return self.client.post(url, dict(upload=self.upload.uuid.hex,
|
||||
platform=platform.id, beta=True))
|
||||
|
||||
def test_add_multi_file_beta(self):
|
||||
|
@ -3393,7 +3399,7 @@ class UploadAddon(object):
|
|||
|
||||
def post(self, supported_platforms=[amo.PLATFORM_ALL], expect_errors=False,
|
||||
source=None, is_listed=True, is_sideload=False, status_code=200):
|
||||
d = dict(upload=self.upload.uuid, source=source,
|
||||
d = dict(upload=self.upload.uuid.hex, source=source,
|
||||
supported_platforms=[p.id for p in supported_platforms],
|
||||
is_unlisted=not is_listed, is_sideload=is_sideload)
|
||||
r = self.client.post(self.url, d, follow=True)
|
||||
|
@ -3550,7 +3556,7 @@ class TestCreateAddon(BaseUploadTest, UploadAddon, TestCase):
|
|||
assert mock_sign_file.called
|
||||
|
||||
def test_missing_platforms(self):
|
||||
r = self.client.post(self.url, dict(upload=self.upload.uuid))
|
||||
r = self.client.post(self.url, dict(upload=self.upload.uuid.hex))
|
||||
assert r.status_code == 200
|
||||
assert r.context['new_addon_form'].errors.as_text() == (
|
||||
'* supported_platforms\n * Need at least one platform.')
|
||||
|
|
|
@ -36,7 +36,7 @@ class TestUploadValidation(BaseUploadTest):
|
|||
def test_no_html_in_messages(self):
|
||||
upload = FileUpload.objects.get(name='invalid-id-20101206.xpi')
|
||||
resp = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid, 'json']))
|
||||
args=[upload.uuid.hex, 'json']))
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content)
|
||||
msg = data['validation']['messages'][1]
|
||||
|
@ -49,7 +49,7 @@ class TestUploadValidation(BaseUploadTest):
|
|||
def test_date_on_upload(self):
|
||||
upload = FileUpload.objects.get(name='invalid-id-20101206.xpi')
|
||||
resp = self.client.get(reverse('devhub.upload_detail',
|
||||
args=[upload.uuid]))
|
||||
args=[upload.uuid.hex]))
|
||||
assert resp.status_code == 200
|
||||
doc = pq(resp.content)
|
||||
assert doc('td').text() == 'December 6, 2010'
|
||||
|
|
|
@ -13,7 +13,7 @@ from django.core.exceptions import PermissionDenied
|
|||
from django.core.files.storage import default_storage as storage
|
||||
from django.db import transaction
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template import Context, loader
|
||||
from django.utils.http import urlquote
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy as _lazy
|
||||
|
@ -36,7 +36,7 @@ from olympia.amo import messages
|
|||
from olympia.amo.decorators import json_view, login_required, post_required
|
||||
from olympia.amo.helpers import absolutify, urlparams
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import escape_all, MenuItem, send_mail
|
||||
from olympia.amo.utils import escape_all, MenuItem, send_mail, render
|
||||
from olympia.api.models import APIKey
|
||||
from olympia.applications.models import AppVersion
|
||||
from olympia.devhub.decorators import dev_required
|
||||
|
@ -613,7 +613,7 @@ def handle_upload(filedata, user, app_id=None, version_id=None, addon=None,
|
|||
|
||||
upload = FileUpload.from_post(filedata, filedata.name, filedata.size,
|
||||
automated_signing=automated, addon=addon)
|
||||
log.info('FileUpload created: %s' % upload.uuid)
|
||||
log.info('FileUpload created: %s' % upload.uuid.hex)
|
||||
if user.is_authenticated():
|
||||
upload.user = user
|
||||
upload.save()
|
||||
|
@ -648,11 +648,11 @@ def upload(request, addon=None, is_standalone=False, is_listed=True,
|
|||
disallow_preliminary_review=no_prelim)
|
||||
if addon:
|
||||
return redirect('devhub.upload_detail_for_addon',
|
||||
addon.slug, upload.uuid)
|
||||
addon.slug, upload.uuid.hex)
|
||||
elif is_standalone:
|
||||
return redirect('devhub.standalone_upload_detail', upload.uuid)
|
||||
return redirect('devhub.standalone_upload_detail', upload.uuid.hex)
|
||||
else:
|
||||
return redirect('devhub.upload_detail', upload.uuid, 'json')
|
||||
return redirect('devhub.upload_detail', upload.uuid.hex, 'json')
|
||||
|
||||
|
||||
@post_required
|
||||
|
@ -862,10 +862,12 @@ def upload_validation_context(request, upload, addon_slug=None, addon=None,
|
|||
if not url:
|
||||
if addon:
|
||||
url = reverse('devhub.upload_detail_for_addon',
|
||||
args=[addon.slug, upload.uuid])
|
||||
args=[addon.slug, upload.uuid.hex])
|
||||
else:
|
||||
url = reverse('devhub.upload_detail', args=[upload.uuid, 'json'])
|
||||
full_report_url = reverse('devhub.upload_detail', args=[upload.uuid])
|
||||
url = reverse(
|
||||
'devhub.upload_detail',
|
||||
args=[upload.uuid.hex, 'json'])
|
||||
full_report_url = reverse('devhub.upload_detail', args=[upload.uuid.hex])
|
||||
|
||||
validation = upload.processed_validation or ''
|
||||
|
||||
|
@ -874,7 +876,7 @@ def upload_validation_context(request, upload, addon_slug=None, addon=None,
|
|||
validation.get('metadata', {}).get(
|
||||
'processed_by_addons_linter', False))
|
||||
|
||||
return {'upload': upload.uuid,
|
||||
return {'upload': upload.uuid.hex,
|
||||
'validation': validation,
|
||||
'error': None,
|
||||
'url': url,
|
||||
|
@ -901,7 +903,7 @@ def upload_detail(request, uuid, format='html'):
|
|||
upload = get_object_or_404(FileUpload, uuid=uuid)
|
||||
|
||||
validate_url = reverse('devhub.standalone_upload_detail',
|
||||
args=[upload.uuid])
|
||||
args=[upload.uuid.hex])
|
||||
|
||||
if upload.compat_with_app:
|
||||
return _compat_result(request, validate_url,
|
||||
|
@ -1382,12 +1384,17 @@ def version_bounce(request, addon_id, addon, version):
|
|||
@dev_required
|
||||
def version_stats(request, addon_id, addon):
|
||||
qs = Version.objects.filter(addon=addon)
|
||||
reviews = (qs.annotate(reviews=Count('reviews'))
|
||||
.values('id', 'version', 'reviews'))
|
||||
reviews = (qs.annotate(review_count=Count('reviews'))
|
||||
.values('id', 'version', 'review_count'))
|
||||
d = dict((v['id'], v) for v in reviews)
|
||||
files = qs.annotate(files=Count('files')).values_list('id', 'files')
|
||||
for id, files in files:
|
||||
d[id]['files'] = files
|
||||
files = (
|
||||
qs
|
||||
.annotate(file_count=Count('files'))
|
||||
.values_list('id', 'file_count'))
|
||||
for id_, file_count in files:
|
||||
# For backwards compatibility
|
||||
d[id_]['files'] = file_count
|
||||
d[id_]['reviews'] = d[id_].pop('review_count')
|
||||
return d
|
||||
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import datetime
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import smart_text
|
||||
|
||||
import mock
|
||||
from pyquery import PyQuery as pq
|
||||
|
@ -681,7 +681,7 @@ class TestDeletedThemeLookup(TestCase):
|
|||
response = self.client.get(reverse('editors.themes.deleted'))
|
||||
assert response.status_code == 200
|
||||
assert (self.deleted.name.localized_string in
|
||||
smart_unicode(response.content))
|
||||
smart_text(response.content))
|
||||
|
||||
def test_perm(self):
|
||||
self.login('persona_reviewer@mozilla.com')
|
||||
|
|
|
@ -8,7 +8,7 @@ from django.conf import settings
|
|||
from django.core.cache import cache
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.utils.translation import ugettext as _, pgettext
|
||||
|
@ -20,7 +20,7 @@ from olympia.access import acl
|
|||
from olympia.addons.decorators import addon_view, addon_view_factory
|
||||
from olympia.addons.models import Addon, Version
|
||||
from olympia.amo.decorators import json_view, post_required
|
||||
from olympia.amo.utils import paginate
|
||||
from olympia.amo.utils import paginate, render
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.constants.base import REVIEW_LIMITED_DELAY_HOURS
|
||||
from olympia.devhub.models import ActivityLog, AddonLog, CommentLog
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.conf import settings
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import Q
|
||||
from django.forms.formsets import formset_factory
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.datastructures import MultiValueDictKeyError
|
||||
from django.utils.translation import ugettext as _, ungettext as ngettext
|
||||
|
||||
|
@ -15,7 +15,7 @@ from olympia.access import acl
|
|||
from olympia.addons.models import Addon, Persona
|
||||
from olympia.amo.decorators import json_view, post_required
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import paginate
|
||||
from olympia.amo.utils import paginate, render
|
||||
from olympia.devhub.models import ActivityLog
|
||||
from olympia.editors import forms
|
||||
from olympia.editors.models import RereviewQueueTheme, ReviewerScore, ThemeLock
|
||||
|
|
|
@ -6,7 +6,7 @@ import stat
|
|||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage as storage
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.translation import get_language, ugettext as _
|
||||
from django.template.defaultfilters import filesizeformat
|
||||
from validator.testcases.packagelayout import (
|
||||
|
@ -289,8 +289,8 @@ class FileViewer(object):
|
|||
iterate(self.dest)
|
||||
|
||||
for path in all_files:
|
||||
filename = smart_unicode(os.path.basename(path), errors='replace')
|
||||
short = smart_unicode(path[len(self.dest) + 1:], errors='replace')
|
||||
filename = force_text(os.path.basename(path), errors='replace')
|
||||
short = force_text(path[len(self.dest) + 1:], errors='replace')
|
||||
mime, encoding = mimetypes.guess_type(filename)
|
||||
directory = os.path.isdir(path)
|
||||
|
||||
|
|
|
@ -13,12 +13,11 @@ from django.core.files.storage import default_storage as storage
|
|||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.utils.encoding import smart_str, force_text
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
|
||||
import commonware
|
||||
from cache_nuggets.lib import memoize
|
||||
from django_statsd.clients import statsd
|
||||
from uuidfield.fields import UUIDField
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.models import OnChangeMixin, ModelBase, UncachedManagerBase
|
||||
|
@ -134,7 +133,8 @@ class File(OnChangeMixin, ModelBase):
|
|||
else:
|
||||
host = user_media_url('addons')
|
||||
|
||||
return posixpath.join(*map(smart_str, [host, addon.id, self.filename]))
|
||||
return posixpath.join(
|
||||
*map(force_bytes, [host, addon.id, self.filename]))
|
||||
|
||||
def get_url_path(self, src):
|
||||
return self._make_download_url('downloads.file', src)
|
||||
|
@ -327,7 +327,8 @@ class File(OnChangeMixin, ModelBase):
|
|||
log.info(msg % (src, dst))
|
||||
move_stored_file(src, dst)
|
||||
except UnicodeEncodeError:
|
||||
log.error('Move Failure: %s %s' % (smart_str(src), smart_str(dst)))
|
||||
msg = 'Move Failure: %s %s' % (force_bytes(src), force_bytes(dst))
|
||||
log.error(msg)
|
||||
|
||||
def hide_disabled_file(self):
|
||||
"""Move a disabled file to the guarded file path."""
|
||||
|
@ -337,10 +338,10 @@ class File(OnChangeMixin, ModelBase):
|
|||
self.mv(src, dst, 'Moving disabled file: %s => %s')
|
||||
# Remove the file from the mirrors if necessary.
|
||||
if (self.mirror_file_path and
|
||||
storage.exists(smart_str(self.mirror_file_path))):
|
||||
storage.exists(force_bytes(self.mirror_file_path))):
|
||||
log.info('Unmirroring disabled file: %s'
|
||||
% self.mirror_file_path)
|
||||
storage.delete(smart_str(self.mirror_file_path))
|
||||
storage.delete(force_bytes(self.mirror_file_path))
|
||||
|
||||
def unhide_disabled_file(self):
|
||||
if not self.filename:
|
||||
|
@ -371,8 +372,8 @@ class File(OnChangeMixin, ModelBase):
|
|||
copy_stored_file(self.file_path, dst)
|
||||
except UnicodeEncodeError:
|
||||
log.info('Copy Failure: %s %s %s' %
|
||||
(self.id, smart_str(self.filename),
|
||||
smart_str(self.file_path)))
|
||||
(self.id, force_bytes(self.filename),
|
||||
force_bytes(self.file_path)))
|
||||
|
||||
_get_localepicker = re.compile('^locale browser ([\w\-_]+) (.*)$', re.M)
|
||||
|
||||
|
@ -554,7 +555,7 @@ def track_file_status_change(file_):
|
|||
|
||||
class FileUpload(ModelBase):
|
||||
"""Created when a file is uploaded for validation/submission."""
|
||||
uuid = UUIDField(auto=True)
|
||||
uuid = models.UUIDField(default=uuid.uuid4, editable=False)
|
||||
path = models.CharField(max_length=255, default='')
|
||||
name = models.CharField(max_length=255, default='',
|
||||
help_text="The user's original filename")
|
||||
|
@ -578,7 +579,7 @@ class FileUpload(ModelBase):
|
|||
db_table = 'file_uploads'
|
||||
|
||||
def __unicode__(self):
|
||||
return self.uuid
|
||||
return unicode(self.uuid.hex)
|
||||
|
||||
def save(self, *args, **kw):
|
||||
if self.validation:
|
||||
|
@ -588,8 +589,8 @@ class FileUpload(ModelBase):
|
|||
|
||||
def add_file(self, chunks, filename, size):
|
||||
if not self.uuid:
|
||||
self.uuid = self._meta.get_field('uuid')._create_uuid().hex
|
||||
filename = smart_str(u'{0}_{1}'.format(self.uuid, filename))
|
||||
self.uuid = self._meta.get_field('uuid')._create_uuid()
|
||||
filename = force_bytes(u'{0}_{1}'.format(self.uuid.hex, filename))
|
||||
loc = os.path.join(user_media_path('addons'), 'temp', uuid.uuid4().hex)
|
||||
base, ext = os.path.splitext(smart_path(filename))
|
||||
is_crx = False
|
||||
|
|
|
@ -609,7 +609,7 @@ class TestFileUpload(UploadTest):
|
|||
def test_from_post_filename(self):
|
||||
upload = self.upload()
|
||||
assert upload.uuid
|
||||
assert upload.name == '{0}_filename.xpi'.format(upload.uuid)
|
||||
assert upload.name == '{0}_filename.xpi'.format(upload.uuid.hex)
|
||||
|
||||
def test_from_post_hash(self):
|
||||
hash = hashlib.sha256(self.data).hexdigest()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from django import http, shortcuts
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import render
|
||||
from django.views.decorators.cache import never_cache
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import condition
|
||||
|
@ -13,7 +12,7 @@ from cache_nuggets.lib import Message, Token
|
|||
from olympia.access import acl
|
||||
from olympia.amo.decorators import json_view
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import HttpResponseSendFile, urlparams
|
||||
from olympia.amo.utils import HttpResponseSendFile, urlparams, render
|
||||
from olympia.files.decorators import (
|
||||
etag, file_view, compare_file_view, file_view_token, last_modified)
|
||||
from olympia.files.tasks import extract_file
|
||||
|
|
|
@ -2,6 +2,7 @@ from optparse import make_option
|
|||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import translation
|
||||
|
||||
from olympia.landfill.generators import generate_addons
|
||||
|
||||
|
@ -40,7 +41,10 @@ class Command(BaseCommand):
|
|||
if not settings.DEBUG:
|
||||
raise CommandError('You can only run this command with your '
|
||||
'DEBUG setting set to True.')
|
||||
|
||||
num = int(args[0])
|
||||
email = kwargs.get('email')
|
||||
app_name = kwargs.get('app_name')
|
||||
generate_addons(num, email, app_name)
|
||||
|
||||
with translation.override(settings.LANGUAGE_CODE):
|
||||
generate_addons(num, email, app_name)
|
||||
|
|
|
@ -2,6 +2,7 @@ from optparse import make_option
|
|||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import translation
|
||||
|
||||
from olympia.landfill.generators import generate_themes
|
||||
|
||||
|
@ -37,4 +38,6 @@ class Command(BaseCommand):
|
|||
'DEBUG setting set to True.')
|
||||
num = int(args[0])
|
||||
email = kwargs.get('email')
|
||||
generate_themes(num, email)
|
||||
|
||||
with translation.override(settings.LANGUAGE_CODE):
|
||||
generate_themes(num, email)
|
||||
|
|
|
@ -60,8 +60,10 @@ def addon_to_dict(addon, disco=False, src='api'):
|
|||
if v:
|
||||
d['version'] = v.version
|
||||
d['platforms'] = [unicode(a.name) for a in v.supported_platforms]
|
||||
d['compatible_apps'] = v.compatible_apps.values()
|
||||
|
||||
d['compatible_apps'] = [
|
||||
{unicode(amo.APP_IDS[obj.application].pretty): {
|
||||
'min': unicode(obj.min), 'max': unicode(obj.max)}}
|
||||
for obj in v.compatible_apps.values()]
|
||||
if addon.eula:
|
||||
d['eula'] = unicode(addon.eula)
|
||||
|
||||
|
|
|
@ -11,15 +11,15 @@ from datetime import date, timedelta
|
|||
from django.core.cache import cache
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.http import HttpResponse, HttpResponsePermanentRedirect
|
||||
from django.template.context import get_standard_processors
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy, get_language
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
import commonware.log
|
||||
import jingo
|
||||
import waffle
|
||||
from caching.base import cached_with
|
||||
from jingo import get_standard_processors
|
||||
|
||||
from olympia import amo, legacy_api
|
||||
from olympia.addons.models import Addon, CompatOverride
|
||||
|
@ -27,7 +27,7 @@ from olympia.amo.decorators import (
|
|||
allow_cross_site_request, json_view)
|
||||
from olympia.amo.models import manual_order
|
||||
from olympia.amo.urlresolvers import get_url_prefix
|
||||
from olympia.amo.utils import JSONEncoder
|
||||
from olympia.amo.utils import AMOJSONEncoder
|
||||
from olympia.legacy_api.utils import addon_to_dict, extract_filters
|
||||
from olympia.search.views import (
|
||||
AddonSuggestionsAjax, PersonaSuggestionsAjax, name_query)
|
||||
|
@ -276,7 +276,7 @@ class AddonDetailView(APIView):
|
|||
return self.render('legacy_api/addon_detail.xml', {'addon': addon})
|
||||
|
||||
def render_json(self, context):
|
||||
return json.dumps(addon_to_dict(context['addon']), cls=JSONEncoder)
|
||||
return json.dumps(addon_to_dict(context['addon']), cls=AMOJSONEncoder)
|
||||
|
||||
|
||||
@non_atomic_requests
|
||||
|
@ -285,7 +285,7 @@ def guid_search(request, api_version, guids):
|
|||
|
||||
def guid_search_cache_key(guid):
|
||||
key = 'guid_search:%s:%s:%s' % (api_version, lang, guid)
|
||||
return hashlib.md5(smart_str(key)).hexdigest()
|
||||
return hashlib.md5(force_bytes(key)).hexdigest()
|
||||
|
||||
guids = [g.strip() for g in guids.split(',')] if guids else []
|
||||
|
||||
|
@ -465,7 +465,7 @@ class ListView(APIView):
|
|||
def f():
|
||||
return self._process(addons, *args)
|
||||
|
||||
return cached_with(addons, f, map(smart_str, args))
|
||||
return cached_with(addons, f, map(force_bytes, args))
|
||||
|
||||
def _process(self, addons, *args):
|
||||
return self.render('legacy_api/list.xml',
|
||||
|
@ -473,7 +473,7 @@ class ListView(APIView):
|
|||
|
||||
def render_json(self, context):
|
||||
return json.dumps([addon_to_dict(a) for a in context['addons']],
|
||||
cls=JSONEncoder)
|
||||
cls=AMOJSONEncoder)
|
||||
|
||||
|
||||
class LanguageView(APIView):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.core.cache import cache
|
||||
from django.test.utils import override_settings
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.translation import trim_whitespace
|
||||
|
||||
import mock
|
||||
|
@ -127,7 +127,7 @@ class TestPromos(TestCase):
|
|||
def _test_response_contains_addons(self, response):
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
content = smart_unicode(response.content)
|
||||
content = smart_text(response.content)
|
||||
assert unicode(self.addon1.name) in content
|
||||
assert unicode(self.addon2.name) in content
|
||||
assert unicode(self.addon3.name) in content
|
||||
|
@ -179,7 +179,7 @@ class TestPromos(TestCase):
|
|||
response = self.client.get(self.get_disco_url('10.0', 'Darwin'))
|
||||
assert response.status_code == 200
|
||||
assert response.content
|
||||
content = smart_unicode(response.content)
|
||||
content = smart_text(response.content)
|
||||
assert unicode(self.addon1.name) not in content
|
||||
assert unicode(self.addon2.name) in content
|
||||
assert unicode(self.addon3.name) in content
|
||||
|
|
|
@ -3,13 +3,14 @@ import json
|
|||
from django import http
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.forms.models import modelformset_factory
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
||||
import commonware.log
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.models import manual_order
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import render
|
||||
from olympia.addons.decorators import addon_view_factory
|
||||
from olympia.addons.models import Addon
|
||||
from olympia.addons.utils import get_featured_ids
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import datetime
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class ReindexingManager(models.Manager):
|
||||
|
@ -54,7 +54,7 @@ class Reindexing(models.Model):
|
|||
SITE_CHOICES = (
|
||||
('amo', 'AMO'),
|
||||
)
|
||||
start_date = models.DateTimeField(default=datetime.datetime.now())
|
||||
start_date = models.DateTimeField(default=timezone.now)
|
||||
old_index = models.CharField(max_length=255, null=True)
|
||||
new_index = models.CharField(max_length=255)
|
||||
alias = models.CharField(max_length=255)
|
||||
|
|
|
@ -48,8 +48,16 @@ def path(*folders):
|
|||
ROOT_PACKAGE = os.path.basename(ROOT)
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
DEBUG_PROPAGATE_EXCEPTIONS = True
|
||||
SILENCED_SYSTEM_CHECKS = (
|
||||
# Recommendation to use OneToOneField instead of ForeignKey(unique=True)
|
||||
# but our translations are the way they are...
|
||||
'fields.W342',
|
||||
|
||||
# TEMPLATE_DIRS is required by jingo, remove this line here once we
|
||||
# get rid of jingo
|
||||
'1_8.W001',
|
||||
)
|
||||
|
||||
# LESS CSS OPTIONS (Debug only).
|
||||
LESS_PREPROCESS = True # Compile LESS with Node, rather than client-side JS?
|
||||
|
@ -250,21 +258,12 @@ SECRET_KEY = 'this-is-a-dummy-key-and-its-overridden-for-prod-servers'
|
|||
|
||||
# Templates
|
||||
|
||||
# List of callables that know how to import templates from various sources.
|
||||
TEMPLATE_LOADERS = (
|
||||
'olympia.lib.template_loader.Loader',
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)
|
||||
|
||||
# We don't want jingo's template loaded to pick up templates for third party
|
||||
# apps that don't use Jinja2. The Following is a list of prefixes for jingo to
|
||||
# ignore.
|
||||
JINGO_EXCLUDE_APPS = (
|
||||
'django_extensions',
|
||||
'admin',
|
||||
'toolbar_statsd',
|
||||
'registration',
|
||||
'rest_framework',
|
||||
'waffle',
|
||||
)
|
||||
|
@ -275,29 +274,46 @@ JINGO_EXCLUDE_PATHS = (
|
|||
'editors/emails',
|
||||
'amo/emails',
|
||||
'devhub/email/revoked-key-email.ltxt',
|
||||
'devhub/email/new-key-email.ltxt'
|
||||
'devhub/email/new-key-email.ltxt',
|
||||
|
||||
# Django specific templates
|
||||
'registration/password_reset_subject.txt'
|
||||
)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS = (
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.request',
|
||||
'session_csrf.context_processor',
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': (
|
||||
path('media', 'docs'),
|
||||
path('src/olympia/templates'),
|
||||
),
|
||||
'OPTIONS': {
|
||||
'context_processors': (
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.core.context_processors.debug',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.request',
|
||||
'session_csrf.context_processor',
|
||||
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
|
||||
'olympia.amo.context_processors.app',
|
||||
'olympia.amo.context_processors.i18n',
|
||||
'olympia.amo.context_processors.global_settings',
|
||||
'olympia.amo.context_processors.static_url',
|
||||
'jingo_minify.helpers.build_ids',
|
||||
)
|
||||
'olympia.amo.context_processors.app',
|
||||
'olympia.amo.context_processors.i18n',
|
||||
'olympia.amo.context_processors.global_settings',
|
||||
'olympia.amo.context_processors.static_url',
|
||||
'jingo_minify.helpers.build_ids',
|
||||
),
|
||||
'loaders': (
|
||||
'olympia.lib.template_loader.Loader',
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
path('media', 'docs'),
|
||||
path('src/olympia/templates'),
|
||||
)
|
||||
# jingo still looks at TEMPLATE_DIRS
|
||||
TEMPLATE_DIRS = TEMPLATES[0]['DIRS']
|
||||
|
||||
|
||||
def JINJA_CONFIG():
|
||||
|
@ -328,6 +344,10 @@ def JINJA_CONFIG():
|
|||
config['bytecode_cache'] = bc
|
||||
return config
|
||||
|
||||
X_FRAME_OPTIONS = 'DENY'
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
SECURE_HSTS_SECONDS = 31536000
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
# AMO URL middleware comes first so everyone else sees nice URLs.
|
||||
|
@ -342,10 +362,9 @@ MIDDLEWARE_CLASSES = (
|
|||
# Munging REMOTE_ADDR must come before ThreadRequest.
|
||||
'commonware.middleware.SetRemoteAddrFromForwardedFor',
|
||||
|
||||
'commonware.middleware.FrameOptionsHeader',
|
||||
'commonware.middleware.XSSProtectionHeader',
|
||||
'commonware.middleware.ContentTypeOptionsHeader',
|
||||
'commonware.middleware.StrictTransportMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
|
||||
'multidb.middleware.PinningRouterMiddleware',
|
||||
'waffle.middleware.WaffleMiddleware',
|
||||
|
||||
|
@ -1480,19 +1499,6 @@ MIN_NOT_D2C_VERSION = '37'
|
|||
# True when the Django app is running from the test suite.
|
||||
IN_TEST_SUITE = False
|
||||
|
||||
# The configuration for the client that speaks to solitude.
|
||||
# A tuple of the solitude hosts.
|
||||
SOLITUDE_HOSTS = ('',)
|
||||
|
||||
# The oAuth key and secret that solitude needs.
|
||||
SOLITUDE_KEY = ''
|
||||
SOLITUDE_SECRET = ''
|
||||
# The timeout we'll give solitude.
|
||||
SOLITUDE_TIMEOUT = 10
|
||||
|
||||
# The OAuth keys to connect to the solitude host specified above.
|
||||
SOLITUDE_OAUTH = {'key': '', 'secret': ''}
|
||||
|
||||
# Temporary flag to work with navigator.mozPay() on devices that don't
|
||||
# support it natively.
|
||||
SIMULATE_NAV_PAY = False
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
UPDATE hubrsskeys SET rsskey=REPLACE(rsskey,'-','');
|
|
@ -2,10 +2,10 @@ from collections import defaultdict
|
|||
|
||||
from django.conf import settings
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import render
|
||||
|
||||
from olympia.devhub.models import ActivityLog
|
||||
from olympia.users.models import UserProfile
|
||||
from olympia.amo.utils import render
|
||||
|
||||
|
||||
@non_atomic_requests
|
||||
|
|
|
@ -6,7 +6,7 @@ from django import http
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.template import Context, loader
|
||||
from django.utils.http import urlquote
|
||||
from django.utils.translation import ugettext as _
|
||||
|
@ -23,7 +23,8 @@ from olympia import amo
|
|||
from olympia.amo import messages
|
||||
from olympia.amo.decorators import (
|
||||
json_view, login_required, post_required, restricted_content)
|
||||
from olympia.amo import helpers, utils as amo_utils
|
||||
from olympia.amo import helpers
|
||||
from olympia.amo.utils import render, paginate, send_mail as amo_send_mail
|
||||
from olympia.access import acl
|
||||
from olympia.addons.decorators import addon_view_factory
|
||||
from olympia.addons.models import Addon
|
||||
|
@ -42,9 +43,9 @@ addon_view = addon_view_factory(qs=Addon.objects.valid)
|
|||
|
||||
def send_mail(template, subject, emails, context, perm_setting):
|
||||
template = loader.get_template(template)
|
||||
amo_utils.send_mail(subject, template.render(Context(context,
|
||||
autoescape=False)),
|
||||
recipient_list=emails, perm_setting=perm_setting)
|
||||
amo_send_mail(
|
||||
subject, template.render(Context(context, autoescape=False)),
|
||||
recipient_list=emails, perm_setting=perm_setting)
|
||||
|
||||
|
||||
@addon_view
|
||||
|
@ -76,7 +77,7 @@ def review_list(request, addon, review_id=None, user_id=None, template=None):
|
|||
ctx['page'] = 'list'
|
||||
qs = qs.filter(is_latest=True)
|
||||
|
||||
ctx['reviews'] = reviews = amo_utils.paginate(request, qs)
|
||||
ctx['reviews'] = reviews = paginate(request, qs)
|
||||
ctx['replies'] = Review.get_replies(reviews.object_list)
|
||||
if request.user.is_authenticated():
|
||||
ctx['review_perms'] = {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import logging
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
from elasticsearch import TransportError
|
||||
|
||||
from olympia.amo.utils import render
|
||||
|
||||
|
||||
log = logging.getLogger('z.es')
|
||||
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
from django import http
|
||||
from django.db.models import Q
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import render
|
||||
from django.utils import translation
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
|
||||
|
@ -16,7 +15,7 @@ from olympia.browse.views import personas_listing as personas_listing_view
|
|||
from olympia.addons.models import Addon, Category
|
||||
from olympia.amo.decorators import json_view
|
||||
from olympia.amo.helpers import locale_url, urlparams
|
||||
from olympia.amo.utils import sorted_groupby
|
||||
from olympia.amo.utils import sorted_groupby, render
|
||||
from olympia.bandwagon.models import Collection
|
||||
from olympia.versions.compare import dict_from_int, version_dict, version_int
|
||||
|
||||
|
@ -601,7 +600,7 @@ def tag_sidebar(request, form_data, aggregations):
|
|||
|
||||
|
||||
def fix_search_query(query, extra_params=None):
|
||||
rv = dict((smart_str(k), v) for k, v in query.items())
|
||||
rv = dict((force_bytes(k), v) for k, v in query.items())
|
||||
changed = False
|
||||
# Change old keys to new names.
|
||||
keys = {
|
||||
|
|
|
@ -17,7 +17,7 @@ class FileUploadSerializer(serializers.ModelSerializer):
|
|||
passed_review = serializers.SerializerMethodField()
|
||||
|
||||
# For backwards-compatibility reasons, we return the uuid as "pk".
|
||||
pk = serializers.CharField(source='uuid')
|
||||
pk = serializers.UUIDField(source='uuid', format='hex')
|
||||
processed = serializers.BooleanField()
|
||||
reviewed = serializers.SerializerMethodField()
|
||||
valid = serializers.BooleanField(source='passed_all_validations')
|
||||
|
@ -49,11 +49,11 @@ class FileUploadSerializer(serializers.ModelSerializer):
|
|||
def get_url(self, instance):
|
||||
return absolutify(reverse('signing.version', args=[instance.addon.guid,
|
||||
instance.version,
|
||||
instance.uuid]))
|
||||
instance.uuid.hex]))
|
||||
|
||||
def get_validation_url(self, instance):
|
||||
return absolutify(reverse('devhub.upload_detail',
|
||||
args=[instance.uuid]))
|
||||
args=[instance.uuid.hex]))
|
||||
|
||||
def get_files(self, instance):
|
||||
if self.version is not None:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
# -*- coding: utf-8 -*
|
||||
import os
|
||||
import json
|
||||
|
@ -487,10 +488,10 @@ class TestCheckVersion(BaseUploadVersionCase):
|
|||
newer_upload = FileUpload.objects.latest()
|
||||
assert newer_upload != upload
|
||||
|
||||
response = self.get(self.url(self.guid, '3.0', upload.uuid))
|
||||
response = self.get(self.url(self.guid, '3.0', upload.uuid.hex))
|
||||
assert response.status_code == 200
|
||||
# For backwards-compatibility reasons, we return the uuid as "pk".
|
||||
assert response.data['pk'] == upload.uuid
|
||||
assert response.data['pk'] == upload.uuid.hex
|
||||
assert 'processed' in response.data
|
||||
|
||||
def test_version_exists_with_pk_not_owner(self):
|
||||
|
@ -506,7 +507,8 @@ class TestCheckVersion(BaseUploadVersionCase):
|
|||
upload = FileUpload.objects.latest()
|
||||
|
||||
# Check that the user that created the upload can access it properly.
|
||||
response = self.get(self.url('@create-version', '1.0', upload.uuid))
|
||||
response = self.get(
|
||||
self.url('@create-version', '1.0', upload.uuid.hex))
|
||||
assert response.status_code == 200
|
||||
assert 'processed' in response.data
|
||||
|
||||
|
@ -517,7 +519,7 @@ class TestCheckVersion(BaseUploadVersionCase):
|
|||
|
||||
# Check that we can't access the FileUpload by uuid even if we pass in
|
||||
# an add-on and version that we own if we don't own the FileUpload.
|
||||
response = self.get(self.url(self.guid, '3.0', upload.uuid))
|
||||
response = self.get(self.url(self.guid, '3.0', upload.uuid.hex))
|
||||
assert response.status_code == 404
|
||||
assert 'error' in response.data
|
||||
|
||||
|
|
|
@ -314,12 +314,19 @@ class TestCSVs(ESStatsTest):
|
|||
self.url_args = {'start': '20200101', 'end': '20200130', 'addon_id': 4}
|
||||
response = self.get_view_response('stats.versions_series', head=True,
|
||||
group='day', format='csv')
|
||||
assert response["cache-control"] == 'max-age=0'
|
||||
|
||||
assert (
|
||||
set(response['cache-control'].split(', ')),
|
||||
{'max-age=0', 'no-cache', 'no-store', 'must-revalidate'},
|
||||
)
|
||||
|
||||
self.url_args = {'start': '20200101', 'end': '20200130', 'addon_id': 4}
|
||||
response = self.get_view_response('stats.versions_series', head=True,
|
||||
group='day', format='json')
|
||||
assert response["cache-control"] == 'max-age=0'
|
||||
assert (
|
||||
set(response['cache-control'].split(', ')),
|
||||
{'max-age=0', 'no-cache', 'no-store', 'must-revalidate'},
|
||||
)
|
||||
|
||||
def test_usage_series_no_data(self):
|
||||
url_args = [
|
||||
|
|
|
@ -6,7 +6,6 @@ import logging
|
|||
import os
|
||||
import time
|
||||
from datetime import date, timedelta, datetime
|
||||
from types import GeneratorType
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
|
@ -15,7 +14,7 @@ from django.core.files.storage import get_storage_class
|
|||
from django.db import connection
|
||||
from django.db.models import Avg, Count, Q, Sum
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.cache import add_never_cache_headers, patch_cache_control
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
|
@ -28,7 +27,7 @@ from rest_framework.response import Response
|
|||
from rest_framework.views import APIView
|
||||
|
||||
from olympia import amo
|
||||
from olympia.amo.utils import DecimalJSONEncoder
|
||||
from olympia.amo.utils import render, AMOJSONEncoder
|
||||
from olympia.access import acl
|
||||
from olympia.addons.decorators import addon_view_factory
|
||||
from olympia.addons.models import Addon
|
||||
|
@ -700,13 +699,9 @@ def render_json(request, addon, stats):
|
|||
"""Render a stats series in JSON."""
|
||||
response = http.HttpResponse(content_type='text/json')
|
||||
|
||||
# XXX: Subclass DjangoJSONEncoder to handle generators.
|
||||
if isinstance(stats, GeneratorType):
|
||||
stats = list(stats)
|
||||
|
||||
# Django's encoder supports date and datetime.
|
||||
fudge_headers(response, stats)
|
||||
json.dump(stats, response, cls=DecimalJSONEncoder)
|
||||
json.dump(stats, response, cls=AMOJSONEncoder)
|
||||
return response
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.conf import settings
|
||||
from django.utils import translation
|
||||
from django.utils.translation.trans_real import to_language
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
import bleach
|
||||
import jinja2
|
||||
|
@ -43,7 +43,7 @@ def truncate(s, length=255, killwords=True, end='...'):
|
|||
return ''
|
||||
if hasattr(s, '__truncate__'):
|
||||
return s.__truncate__(length, killwords, end)
|
||||
return jinja2.filters.do_truncate(smart_unicode(s), length, killwords, end)
|
||||
return jinja2.filters.do_truncate(force_text(s), length, killwords, end)
|
||||
|
||||
|
||||
@jingo.register.inclusion_tag('translations/trans-menu.html')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from django.db import connections, models, router
|
||||
from django.db.models.deletion import Collector
|
||||
from django.utils import encoding
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
import bleach
|
||||
import commonware.log
|
||||
|
@ -112,7 +112,7 @@ class Translation(ModelBase):
|
|||
# Like in ModelBase, we avoid putting the real db in the key because it
|
||||
# does us more harm than good.
|
||||
key_parts = ('o', 'translations.translation', pk, 'default')
|
||||
return ':'.join(map(encoding.smart_unicode, key_parts))
|
||||
return ':'.join(map(force_text, key_parts))
|
||||
|
||||
@classmethod
|
||||
def new(cls, string, locale, id=None):
|
||||
|
@ -129,18 +129,22 @@ class Translation(ModelBase):
|
|||
if id is None:
|
||||
# Get a sequence key for the new translation.
|
||||
cursor = connections['default'].cursor()
|
||||
cursor.execute("""UPDATE translations_seq
|
||||
SET id=LAST_INSERT_ID(
|
||||
id + @@global.auto_increment_increment)""")
|
||||
cursor.execute("""
|
||||
UPDATE translations_seq
|
||||
SET id=LAST_INSERT_ID(
|
||||
id + @@global.auto_increment_increment
|
||||
)
|
||||
""")
|
||||
|
||||
# The sequence table should never be empty. But alas, if it is,
|
||||
# let's fix it.
|
||||
if not cursor.rowcount > 0:
|
||||
cursor.execute("""INSERT INTO translations_seq (id)
|
||||
VALUES(LAST_INSERT_ID(
|
||||
id + @@global.auto_increment_increment)
|
||||
)""")
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO translations_seq (id)
|
||||
VALUES(LAST_INSERT_ID(
|
||||
id + @@global.auto_increment_increment
|
||||
))
|
||||
""")
|
||||
cursor.execute('SELECT LAST_INSERT_ID()')
|
||||
id = cursor.fetchone()[0]
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ import itertools
|
|||
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
|
||||
from django.utils import translation as translation_utils
|
||||
|
||||
from olympia.addons.query import IndexCompiler, IndexQuery
|
||||
from django.db.models.sql import compiler
|
||||
from django.db.models.sql.constants import LOUTER
|
||||
from django.db.models.sql.datastructures import Join
|
||||
|
||||
|
||||
def order_by_translation(qs, fieldname):
|
||||
|
@ -26,10 +26,6 @@ def order_by_translation(qs, fieldname):
|
|||
model = qs.model
|
||||
field = model._meta.get_field(fieldname)
|
||||
|
||||
# connection is a tuple (lhs, table, join_cols)
|
||||
connection = (model._meta.db_table, field.rel.to._meta.db_table,
|
||||
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:
|
||||
|
@ -43,10 +39,15 @@ def order_by_translation(qs, fieldname):
|
|||
# remove results and happily simplifies the LEFT OUTER JOINs to
|
||||
# INNER JOINs)
|
||||
qs.query = qs.query.clone(TranslationQuery)
|
||||
t1 = qs.query.join(connection, join_field=field, reuse=set(),
|
||||
nullable=True)
|
||||
t2 = qs.query.join(connection, join_field=field, reuse=set(),
|
||||
nullable=True)
|
||||
t1 = qs.query.join(
|
||||
Join(field.rel.to._meta.db_table, model._meta.db_table,
|
||||
None, LOUTER, field, True),
|
||||
reuse=set())
|
||||
t2 = qs.query.join(
|
||||
Join(field.rel.to._meta.db_table, model._meta.db_table,
|
||||
None, LOUTER, field, True),
|
||||
reuse=set())
|
||||
|
||||
qs.query.translation_aliases = {field: (t1, t2)}
|
||||
|
||||
f1, f2 = '%s.`localized_string`' % t1, '%s.`localized_string`' % t2
|
||||
|
@ -58,7 +59,7 @@ def order_by_translation(qs, fieldname):
|
|||
order_by=[prefix + name])
|
||||
|
||||
|
||||
class TranslationQuery(IndexQuery):
|
||||
class TranslationQuery(models.sql.query.Query):
|
||||
"""
|
||||
Overrides sql.Query to hit our special compiler that knows how to JOIN
|
||||
translations.
|
||||
|
@ -76,7 +77,7 @@ class TranslationQuery(IndexQuery):
|
|||
return SQLCompiler(self, c.connection, c.using)
|
||||
|
||||
|
||||
class SQLCompiler(IndexCompiler):
|
||||
class SQLCompiler(compiler.SQLCompiler):
|
||||
"""Overrides get_from_clause to LEFT JOIN translations with a locale."""
|
||||
|
||||
def get_from_clause(self):
|
||||
|
@ -84,7 +85,8 @@ class SQLCompiler(IndexCompiler):
|
|||
# doesn't create joins against them.
|
||||
old_tables = list(self.query.tables)
|
||||
for table in itertools.chain(*self.query.translation_aliases.values()):
|
||||
self.query.tables.remove(table)
|
||||
if table in self.query.tables:
|
||||
self.query.tables.remove(table)
|
||||
|
||||
joins, params = super(SQLCompiler, self).get_from_clause()
|
||||
|
||||
|
@ -113,12 +115,12 @@ class SQLCompiler(IndexCompiler):
|
|||
# objects here instead of a bunch of strings.
|
||||
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
|
||||
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)
|
||||
|
||||
join = self.query.alias_map[alias]
|
||||
lhs_col, rhs_col = join.join_cols[0]
|
||||
alias_str = (
|
||||
'' if join.table_alias == join.table_name
|
||||
else ' %s' % join.table_alias)
|
||||
|
||||
if isinstance(fallback, models.Field):
|
||||
fallback_str = '%s.%s' % (qn(self.query.model._meta.db_table),
|
||||
|
@ -127,6 +129,7 @@ class SQLCompiler(IndexCompiler):
|
|||
fallback_str = '%s'
|
||||
|
||||
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(rhs_col),
|
||||
qn(alias), qn('locale'), fallback_str))
|
||||
(join.join_type, qn(join.table_name), alias_str,
|
||||
qn(join.parent_alias), qn2(lhs_col), qn(join.table_alias),
|
||||
qn2(rhs_col), qn(join.table_alias), qn('locale'),
|
||||
fallback_str))
|
||||
|
|
|
@ -12,6 +12,7 @@ class TestForm(forms.TranslationFormMixin, ModelForm):
|
|||
|
||||
class Meta:
|
||||
model = TranslatedModel
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class TestTranslationFormMixin(TestCase):
|
||||
|
|
|
@ -165,7 +165,7 @@ urlpatterns = patterns(
|
|||
|
||||
)
|
||||
|
||||
if settings.TEMPLATE_DEBUG:
|
||||
if settings.DEBUG:
|
||||
# Remove leading and trailing slashes so the regex matches.
|
||||
media_url = settings.MEDIA_URL.lstrip('/').rstrip('/')
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from django.contrib import admin, messages
|
||||
from django.db.utils import IntegrityError
|
||||
from django.shortcuts import render
|
||||
|
||||
from olympia.access.admin import GroupUserInline
|
||||
from olympia.amo.utils import render
|
||||
|
||||
from .models import UserProfile, BlacklistedName, BlacklistedEmailDomain
|
||||
from . import forms
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.conf import settings
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.translation import pgettext
|
||||
|
||||
import jinja2
|
||||
|
@ -77,7 +77,7 @@ def _user_link(user, max_text_length=None):
|
|||
|
||||
return u'<a href="%s" title="%s">%s</a>' % (
|
||||
user.get_url_path(), jinja2.escape(user.name),
|
||||
jinja2.escape(smart_unicode(username)))
|
||||
jinja2.escape(force_text(username)))
|
||||
|
||||
|
||||
@register.filter
|
||||
|
|
|
@ -15,10 +15,11 @@ from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
|
|||
from django.core import validators
|
||||
from django.db import models, transaction
|
||||
from django.template import Context, loader
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _, get_language, activate
|
||||
from django.utils.crypto import constant_time_compare
|
||||
from django.utils.datastructures import SortedDict
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils.encoding import force_bytes, force_text
|
||||
from django.utils.functional import lazy
|
||||
|
||||
import caching.base as caching
|
||||
|
@ -45,7 +46,7 @@ class SHA512PasswordHasher(BasePasswordHasher):
|
|||
assert password is not None
|
||||
assert salt and '$' not in salt
|
||||
hash = hashlib.new(self.algorithm,
|
||||
smart_str(salt + password)).hexdigest()
|
||||
force_bytes(salt + password)).hexdigest()
|
||||
return "%s$%s$%s" % (self.algorithm, salt, hash)
|
||||
|
||||
def verify(self, password, encoded):
|
||||
|
@ -75,9 +76,9 @@ def get_hexdigest(algorithm, salt, raw_password):
|
|||
# users from getpersonas.com. The password is md5 hashed
|
||||
# and then sha512'd.
|
||||
md5 = hashlib.new('md5', raw_password).hexdigest()
|
||||
return hashlib.new('sha512', smart_str(salt + md5)).hexdigest()
|
||||
return hashlib.new('sha512', force_bytes(salt + md5)).hexdigest()
|
||||
|
||||
return hashlib.new(algorithm, smart_str(salt + raw_password)).hexdigest()
|
||||
return hashlib.new(algorithm, force_bytes(salt + raw_password)).hexdigest()
|
||||
|
||||
|
||||
def rand_string(length):
|
||||
|
@ -99,7 +100,7 @@ class UserForeignKey(models.ForeignKey):
|
|||
"""
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(UserForeignKey, self).__init__(UserProfile, *args, **kw)
|
||||
super(UserForeignKey, self).__init__('users.UserProfile', *args, **kw)
|
||||
|
||||
def value_from_object(self, obj):
|
||||
return getattr(obj, self.name).email
|
||||
|
@ -131,7 +132,10 @@ class UserManager(BaseUserManager, ManagerBase):
|
|||
def create_user(self, username, email, password=None, fxa_id=None):
|
||||
# We'll send username=None when registering through FxA to try and
|
||||
# generate a username from the email.
|
||||
user = self.model(username=username, email=email, fxa_id=fxa_id)
|
||||
now = timezone.now()
|
||||
user = self.model(
|
||||
username=username, email=email, fxa_id=fxa_id,
|
||||
last_login=now)
|
||||
if username is None:
|
||||
user.anonymize_username()
|
||||
# FxA won't set a password so don't let a user log in with one.
|
||||
|
@ -157,8 +161,7 @@ class UserManager(BaseUserManager, ManagerBase):
|
|||
AbstractBaseUser._meta.get_field('password').max_length = 255
|
||||
|
||||
|
||||
class UserProfile(OnChangeMixin, ModelBase,
|
||||
AbstractBaseUser):
|
||||
class UserProfile(OnChangeMixin, ModelBase, AbstractBaseUser):
|
||||
objects = UserManager()
|
||||
USERNAME_FIELD = 'username'
|
||||
REQUIRED_FIELDS = ['email']
|
||||
|
@ -166,7 +169,7 @@ class UserProfile(OnChangeMixin, ModelBase,
|
|||
display_name = models.CharField(max_length=255, default='', null=True,
|
||||
blank=True)
|
||||
|
||||
email = models.EmailField(unique=True, null=True)
|
||||
email = models.EmailField(unique=True, null=True, max_length=75)
|
||||
|
||||
averagerating = models.CharField(max_length=255, blank=True, null=True)
|
||||
bio = NoLinksField(short=False)
|
||||
|
@ -206,7 +209,7 @@ class UserProfile(OnChangeMixin, ModelBase,
|
|||
def __init__(self, *args, **kw):
|
||||
super(UserProfile, self).__init__(*args, **kw)
|
||||
if self.username:
|
||||
self.username = smart_unicode(self.username)
|
||||
self.username = force_text(self.username)
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s: %s' % (self.id, self.display_name or self.username)
|
||||
|
@ -346,14 +349,14 @@ class UserProfile(OnChangeMixin, ModelBase,
|
|||
@property
|
||||
def name(self):
|
||||
if self.display_name:
|
||||
return smart_unicode(self.display_name)
|
||||
return force_text(self.display_name)
|
||||
elif self.has_anonymous_username():
|
||||
# L10n: {id} will be something like "13ad6a", just a random number
|
||||
# to differentiate this user from other anonymous users.
|
||||
return _('Anonymous user {id}').format(
|
||||
id=self._anonymous_username_id())
|
||||
else:
|
||||
return smart_unicode(self.username)
|
||||
return force_text(self.username)
|
||||
|
||||
welcome_name = name
|
||||
|
||||
|
@ -664,7 +667,7 @@ class BlacklistedPassword(ModelBase):
|
|||
|
||||
|
||||
class UserHistory(ModelBase):
|
||||
email = models.EmailField()
|
||||
email = models.EmailField(max_length=75)
|
||||
user = models.ForeignKey(UserProfile, related_name='history')
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -8,7 +8,8 @@ from django.conf import settings
|
|||
from django.contrib.auth.hashers import (is_password_usable, check_password,
|
||||
make_password, identify_hasher)
|
||||
from django.core import mail
|
||||
from django.utils import encoding, translation
|
||||
from django.utils import translation
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
import pytest
|
||||
from mock import patch
|
||||
|
@ -343,7 +344,7 @@ class TestPasswords(TestCase):
|
|||
assert u.has_usable_password() is True
|
||||
|
||||
def test_valid_old_password(self):
|
||||
hsh = hashlib.md5(encoding.smart_str(self.utf)).hexdigest()
|
||||
hsh = hashlib.md5(force_bytes(self.utf)).hexdigest()
|
||||
u = UserProfile(password=hsh)
|
||||
assert u.check_password(self.utf) is True
|
||||
# Make sure we updated the old password.
|
||||
|
|
|
@ -9,7 +9,7 @@ from django.core import mail
|
|||
from django.core.cache import cache
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.forms.models import model_to_dict
|
||||
from django.utils.encoding import smart_unicode
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.http import urlsafe_base64_encode
|
||||
|
||||
from lxml.html import fromstring, HTMLParser
|
||||
|
@ -921,7 +921,7 @@ class TestRegistration(UserViewBase):
|
|||
|
||||
def test_redirects_to_login(self):
|
||||
"""Register should redirect to login."""
|
||||
response = self.client.get(reverse('users.register'))
|
||||
response = self.client.get(reverse('users.register'), follow=True)
|
||||
self.assert3xx(response, reverse('users.login'), status_code=301)
|
||||
|
||||
|
||||
|
@ -1271,7 +1271,7 @@ class TestThemesProfile(TestCase):
|
|||
|
||||
results = doc('.personas-grid .persona.hovercard')
|
||||
assert results.length == 1
|
||||
assert smart_unicode(
|
||||
assert force_text(
|
||||
results.find('h3').html()) == unicode(self.theme.name)
|
||||
|
||||
def test_bad_user(self):
|
||||
|
|
|
@ -7,8 +7,7 @@ from django.conf import settings
|
|||
from django.contrib import auth
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import (get_list_or_404, get_object_or_404, redirect,
|
||||
render)
|
||||
from django.shortcuts import get_list_or_404, get_object_or_404, redirect
|
||||
from django.template import Context, loader
|
||||
from django.utils.http import is_safe_url, urlsafe_base64_decode
|
||||
from django.views.decorators.cache import never_cache
|
||||
|
@ -33,7 +32,7 @@ from olympia.amo.decorators import (
|
|||
post_required, write)
|
||||
from olympia.amo.forms import AbuseForm
|
||||
from olympia.amo.urlresolvers import get_url_prefix, reverse
|
||||
from olympia.amo.utils import escape_all, send_mail, urlparams
|
||||
from olympia.amo.utils import escape_all, send_mail, urlparams, render
|
||||
from olympia.bandwagon.models import Collection
|
||||
from olympia.browse.views import PersonasFilter
|
||||
from olympia.users.models import UserNotification
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import re
|
||||
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.encoding import force_bytes
|
||||
|
||||
MAXVERSION = 2 ** 63 - 1
|
||||
|
||||
|
@ -60,7 +60,7 @@ def version_dict(version):
|
|||
|
||||
|
||||
def version_int(version):
|
||||
d = version_dict(smart_str(version))
|
||||
d = version_dict(force_bytes(version))
|
||||
for key in ['alpha_ver', 'major', 'minor1', 'minor2', 'minor3',
|
||||
'pre_ver']:
|
||||
if not d[key]:
|
||||
|
|
|
@ -17,7 +17,6 @@ from django_statsd.clients import statsd
|
|||
from olympia import amo
|
||||
from olympia.amo.models import ManagerBase, ModelBase, OnChangeMixin
|
||||
from olympia.amo.utils import sorted_groupby, utc_millesecs_from_epoch
|
||||
from olympia.addons.query import IndexQuerySet
|
||||
from olympia.amo.decorators import use_master
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.helpers import user_media_path, id_to_path
|
||||
|
@ -46,7 +45,6 @@ class VersionManager(ManagerBase):
|
|||
|
||||
def get_queryset(self):
|
||||
qs = super(VersionManager, self).get_queryset()
|
||||
qs = qs._clone(klass=IndexQuerySet)
|
||||
if not self.include_deleted:
|
||||
qs = qs.exclude(deleted=True)
|
||||
return qs.transform(Version.transformer)
|
||||
|
@ -306,7 +304,7 @@ class Version(OnChangeMixin, ModelBase):
|
|||
@amo.cached_property(writable=True)
|
||||
def compatible_apps(self):
|
||||
"""Get a mapping of {APP: ApplicationVersion}."""
|
||||
avs = self.apps.select_related('versions', 'license')
|
||||
avs = self.apps.select_related('version')
|
||||
return self._compat_map(avs)
|
||||
|
||||
@amo.cached_property
|
||||
|
@ -477,7 +475,7 @@ class Version(OnChangeMixin, ModelBase):
|
|||
|
||||
# FIXME: find out why we have no_cache() here and try to remove it.
|
||||
avs = (ApplicationsVersions.objects.filter(version__in=ids)
|
||||
.select_related('application', 'apps', 'min_set', 'max_set')
|
||||
.select_related('min', 'max')
|
||||
.no_cache())
|
||||
files = File.objects.filter(version__in=ids).no_cache()
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import os
|
|||
|
||||
from django import http
|
||||
from django.db.transaction import non_atomic_requests
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
||||
import caching.base as caching
|
||||
import commonware.log
|
||||
|
@ -10,7 +10,7 @@ from mobility.decorators import mobile_template
|
|||
|
||||
from olympia import amo
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import HttpResponseSendFile, urlparams
|
||||
from olympia.amo.utils import HttpResponseSendFile, urlparams, render
|
||||
from olympia.access import acl
|
||||
from olympia.addons.decorators import (
|
||||
addon_view_factory, owner_or_unlisted_reviewer)
|
||||
|
|
|
@ -1,25 +1,7 @@
|
|||
from django.shortcuts import render
|
||||
import jingo
|
||||
from django.template import loader
|
||||
from django.template.response import SimpleTemplateResponse
|
||||
|
||||
import jingo
|
||||
|
||||
|
||||
def jinja_for_django(template_name, context=None, **kw):
|
||||
"""
|
||||
If you want to use some built in logic (or a contrib app) but need to
|
||||
override the templates to work with Jinja, replace the object's
|
||||
render_to_response function with this one. That will render a Jinja
|
||||
template through Django's functions. An example can be found in the users
|
||||
app.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
context_instance = kw.pop('context_instance')
|
||||
request = context_instance['request']
|
||||
for d in context_instance.dicts:
|
||||
context.update(d)
|
||||
return render(request, template_name, context, **kw)
|
||||
from django.template.backends.django import Template
|
||||
|
||||
|
||||
# We monkeypatch SimpleTemplateResponse.rendered_content to use our jinja
|
||||
|
@ -29,10 +11,9 @@ def jinja_for_django(template_name, context=None, **kw):
|
|||
def rendered_content(self):
|
||||
template = self.template_name
|
||||
context_instance = self.resolve_context(self.context_data)
|
||||
request = context_instance['request']
|
||||
|
||||
# Gross, let's figure out if we're in the admin.
|
||||
if self._current_app == 'admin':
|
||||
if getattr(self._request, 'current_app', None) == 'admin':
|
||||
source = loader.render_to_string(template, context_instance)
|
||||
template = jingo.get_env().from_string(source)
|
||||
# This interferes with our media() helper.
|
||||
|
@ -43,6 +24,10 @@ def rendered_content(self):
|
|||
# not a list.
|
||||
if isinstance(template, (list, tuple)):
|
||||
template = loader.select_template(template)
|
||||
return jingo.render_to_string(request, template, self.context_data)
|
||||
if isinstance(template, Template):
|
||||
template = template.template
|
||||
|
||||
return jingo.render_to_string(
|
||||
self._request, template, self.context_data)
|
||||
|
||||
SimpleTemplateResponse.rendered_content = property(rendered_content)
|
||||
|
|
|
@ -51,7 +51,7 @@ class ValidationJob(ModelBase):
|
|||
related_name='validation_current_set')
|
||||
target_version = models.ForeignKey(AppVersion,
|
||||
related_name='validation_target_set')
|
||||
finish_email = models.EmailField(null=True)
|
||||
finish_email = models.EmailField(null=True, max_length=75)
|
||||
completed = models.DateTimeField(null=True, db_index=True)
|
||||
creator = models.ForeignKey('users.UserProfile', null=True)
|
||||
|
||||
|
@ -242,7 +242,7 @@ class EmailPreview(ModelBase):
|
|||
"""
|
||||
topic = models.CharField(max_length=255, db_index=True)
|
||||
recipient_list = models.TextField() # comma separated list of emails
|
||||
from_email = models.EmailField()
|
||||
from_email = models.EmailField(max_length=75)
|
||||
subject = models.CharField(max_length=255)
|
||||
body = models.TextField()
|
||||
|
||||
|
|
|
@ -1780,11 +1780,10 @@ class TestFileDownload(TestCase):
|
|||
assert resp.status_code == 302
|
||||
|
||||
self.upload = FileUpload.objects.get()
|
||||
self.url = reverse('zadmin.download_file', args=[self.upload.uuid])
|
||||
self.url = reverse('zadmin.download_file', args=[self.upload.uuid.hex])
|
||||
|
||||
def test_download(self):
|
||||
"""Test that downloading file_upload objects works."""
|
||||
|
||||
resp = self.client.get(self.url)
|
||||
assert resp.status_code == 200
|
||||
assert resp.content == self.file.read()
|
||||
|
|
|
@ -12,7 +12,7 @@ from django.core.cache import cache
|
|||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.files.storage import default_storage as storage
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.utils.translation import ugettext_lazy as _lazy
|
||||
from django.utils.html import format_html
|
||||
from django.views import debug
|
||||
|
@ -31,7 +31,7 @@ from olympia.amo.decorators import (
|
|||
any_permission_required, json_view, login_required, post_required)
|
||||
from olympia.amo.mail import DevEmailBackend
|
||||
from olympia.amo.urlresolvers import reverse
|
||||
from olympia.amo.utils import HttpResponseSendFile, chunked
|
||||
from olympia.amo.utils import HttpResponseSendFile, chunked, render
|
||||
from olympia.bandwagon.models import Collection
|
||||
from olympia.compat import FIREFOX_COMPAT
|
||||
from olympia.compat.models import AppCompat, CompatTotals
|
||||
|
|
Загрузка…
Ссылка в новой задаче