Permalink
Browse files

Merge branch 'master' into schema-alteration

  • Loading branch information...
2 parents 76d93a5 + f54a888 commit b31eea069cf9bc801c82a975307a9173527d78f2 @andrewgodwin andrewgodwin committed May 18, 2013
Showing with 2,460 additions and 1,346 deletions.
  1. +1 −1 .gitignore
  2. +2 −1 AUTHORS
  3. +1 −1 django/conf/global_settings.py
  4. +2 −2 django/contrib/admin/options.py
  5. +0 −1 django/contrib/admindocs/tests/__init__.py
  6. +1 −1 django/contrib/admindocs/views.py
  7. +1 −1 django/contrib/auth/forms.py
  8. +8 −5 django/contrib/auth/hashers.py
  9. +0 −15 django/contrib/auth/tests/__init__.py
  10. +21 −0 django/contrib/auth/tests/test_forms.py
  11. +1 −1 django/contrib/auth/tests/test_handlers.py
  12. +2 −2 django/contrib/auth/tests/test_hashers.py
  13. +1 −1 django/contrib/auth/tests/test_management.py
  14. +1 −1 django/contrib/comments/views/utils.py
  15. +1 −1 django/contrib/contenttypes/views.py
  16. +0 −6 django/contrib/flatpages/tests/__init__.py
  17. +0 −2 django/contrib/formtools/tests/__init__.py
  18. +1 −1 django/contrib/formtools/tests/urls.py
  19. +1 −1 django/contrib/formtools/tests/wizard/test_forms.py
  20. +12 −1 django/contrib/gis/db/models/fields.py
  21. +2 −2 django/contrib/gis/db/models/sql/compiler.py
  22. +4 −1 django/contrib/gis/forms/__init__.py
  23. +33 −2 django/contrib/gis/forms/fields.py
  24. +112 −0 django/contrib/gis/forms/widgets.py
  25. +4 −3 django/contrib/gis/gdal/__init__.py
  26. +0 −28 django/contrib/gis/gdal/tests/__init__.py
  27. +9 −10 django/contrib/gis/gdal/tests/test_driver.py
  28. +33 −35 django/contrib/gis/gdal/tests/test_ds.py
  29. +8 −10 django/contrib/gis/gdal/tests/test_envelope.py
  30. +9 −10 django/contrib/gis/gdal/tests/test_geom.py
  31. +7 −9 django/contrib/gis/gdal/tests/test_srs.py
  32. +15 −11 django/contrib/gis/geoip/tests.py
  33. +15 −9 django/contrib/gis/geos/__init__.py
  34. +0 −28 django/contrib/gis/geos/tests/__init__.py
  35. +23 −21 django/contrib/gis/geos/tests/test_geos.py
  36. +13 −14 django/contrib/gis/geos/tests/test_geos_mutation.py
  37. +7 −9 django/contrib/gis/geos/tests/test_io.py
  38. +0 −12 django/contrib/gis/geos/tests/test_mutable_list.py
  39. +1 −1 django/contrib/gis/sitemaps/views.py
  40. +371 −0 django/contrib/gis/static/gis/js/OLMapWidget.js
  41. +1 −1 django/contrib/gis/templates/gis/admin/openlayers.js
  42. +17 −0 django/contrib/gis/templates/gis/openlayers-osm.html
  43. +34 −0 django/contrib/gis/templates/gis/openlayers.html
  44. +2 −95 django/contrib/gis/tests/__init__.py
  45. +20 −11 django/contrib/gis/tests/distapp/tests.py
  46. +14 −5 django/contrib/gis/tests/geo3d/tests.py
  47. +10 −7 django/contrib/gis/tests/geoadmin/tests.py
  48. +6 −1 django/contrib/gis/tests/geoapp/test_feeds.py
  49. +8 −3 django/contrib/gis/tests/geoapp/test_regress.py
  50. +6 −1 django/contrib/gis/tests/geoapp/test_sitemaps.py
  51. +17 −10 django/contrib/gis/tests/geoapp/tests.py
  52. +8 −3 django/contrib/gis/tests/geogapp/tests.py
  53. +9 −3 django/contrib/gis/tests/inspectapp/tests.py
  54. +13 −7 django/contrib/gis/tests/layermap/tests.py
  55. +10 −5 django/contrib/gis/tests/relatedapp/tests.py
  56. +173 −16 django/contrib/gis/tests/test_geoforms.py
  57. +0 −1 django/contrib/gis/tests/test_spatialrefsys.py
  58. +9 −0 django/contrib/gis/tests/utils.py
  59. +1 −1 django/contrib/gis/utils/layermapping.py
  60. +0 −5 django/contrib/messages/tests/__init__.py
  61. +1 −2 django/contrib/messages/views.py
  62. +0 −4 django/contrib/sitemaps/tests/__init__.py
  63. +1 −1 django/contrib/staticfiles/management/commands/collectstatic.py
  64. +2 −6 django/core/files/images.py
  65. +1 −1 django/core/files/storage.py
  66. +1 −1 django/core/management/__init__.py
  67. +2 −2 django/core/management/base.py
  68. +1 −1 django/core/management/commands/createcachetable.py
  69. +2 −2 django/core/management/commands/runserver.py
  70. +2 −6 django/core/management/validation.py
  71. +1 −3 django/core/serializers/base.py
  72. +5 −5 django/db/models/base.py
  73. +2 −2 django/db/models/fields/__init__.py
  74. +1 −1 django/db/models/options.py
  75. +1 −1 django/db/models/query.py
  76. +1 −1 django/db/models/related.py
  77. +2 −2 django/db/models/sql/aggregates.py
  78. +16 −16 django/db/models/sql/compiler.py
  79. +6 −6 django/db/models/sql/query.py
  80. +6 −6 django/db/models/sql/subqueries.py
  81. +4 −12 django/forms/fields.py
  82. +1 −1 django/forms/forms.py
  83. +1 −1 django/forms/formsets.py
  84. +1 −1 django/forms/widgets.py
  85. +1 −1 django/http/multipartparser.py
  86. +1 −1 django/template/base.py
  87. +2 −2 django/template/defaulttags.py
  88. +7 −0 django/test/_doctest.py
  89. +289 −0 django/test/runner.py
  90. +15 −200 django/test/simple.py
  91. +10 −0 django/test/testcases.py
  92. +1 −1 django/utils/dateformat.py
  93. +4 −1 django/utils/html.py
  94. +148 −0 django/utils/image.py
  95. +1 −1 django/utils/log.py
  96. +4 −1 django/utils/translation/trans_real.py
  97. +1 −1 django/utils/tree.py
  98. +1 −1 django/utils/unittest/loader.py
  99. +1 −1 docs/faq/contributing.txt
  100. +1 −2 docs/index.txt
  101. +31 −12 docs/internals/committers.txt
  102. +16 −1 docs/internals/deprecation.txt
  103. +42 −28 docs/intro/tutorial04.txt
  104. +38 −44 docs/intro/tutorial05.txt
  105. +1 −1 docs/ref/class-based-views/base.txt
  106. +4 −3 docs/ref/contrib/admin/admindocs.txt
  107. +165 −0 docs/ref/contrib/gis/forms-api.txt
  108. +1 −0 docs/ref/contrib/gis/index.txt
  109. +9 −48 docs/ref/contrib/gis/testing.txt
  110. +40 −1 docs/ref/django-admin.txt
  111. +9 −5 docs/ref/forms/fields.txt
  112. +3 −0 docs/ref/models/fields.txt
  113. +2 −2 docs/ref/models/querysets.txt
  114. +6 −1 docs/ref/settings.txt
  115. +2 −2 docs/ref/utils.txt
  116. +1 −1 docs/releases/1.2.4.txt
  117. +1 −1 docs/releases/1.3.txt
  118. +71 −0 docs/releases/1.6.txt
  119. +2 −1 docs/topics/auth/customizing.txt
  120. +5 −0 docs/topics/auth/default.txt
  121. +3 −3 docs/topics/auth/passwords.txt
  122. +22 −18 docs/topics/db/transactions.txt
  123. +2 −0 docs/topics/i18n/timezones.txt
  124. +3 −1 docs/topics/i18n/translation.txt
  125. +1 −1 docs/topics/serialization.txt
  126. +44 −33 docs/topics/testing/advanced.txt
  127. +0 −81 docs/topics/testing/doctests.txt
  128. +3 −74 docs/topics/testing/index.txt
  129. +63 −79 docs/topics/testing/overview.txt
  130. +4 −6 extras/csrf_migration_helper.py
  131. +3 −3 tests/admin_scripts/tests.py
  132. +14 −2 tests/admin_views/tests.py
  133. +7 −2 tests/backends/tests.py
  134. +7 −13 tests/file_storage/tests.py
  135. +4 −0 tests/generic_relations/models.py
  136. +6 −1 tests/generic_relations/tests.py
  137. +0 −11 tests/generic_views/tests.py
  138. +0 −6 tests/i18n/tests.py
  139. +6 −11 tests/model_fields/models.py
  140. +6 −2 tests/model_fields/test_imagefield.py
  141. +0 −5 tests/model_fields/tests.py
  142. +3 −9 tests/model_forms/models.py
  143. +56 −38 tests/runtests.py
  144. +1 −1 tests/serializers_regress/models.py
  145. +0 −9 tests/template_tests/tests.py
  146. 0 tests/{test_runner/deprecation_app → test_discovery_sample}/__init__.py
  147. +7 −0 tests/test_discovery_sample/pattern_tests.py
  148. 0 tests/{test_runner/invalid_app/models → test_discovery_sample/tests}/__init__.py
  149. +7 −0 tests/test_discovery_sample/tests/tests.py
  150. +22 −0 tests/test_discovery_sample/tests_sample.py
  151. 0 tests/test_discovery_sample2/__init__.py
  152. +7 −0 tests/test_discovery_sample2/tests.py
  153. +68 −0 tests/test_runner/test_discover_runner.py
  154. +13 −12 tests/test_runner/tests.py
  155. 0 tests/test_runner_deprecation_app/__init__.py
  156. 0 tests/{test_runner/deprecation_app → test_runner_deprecation_app}/models.py
  157. +0 −2 tests/{test_runner/deprecation_app → test_runner_deprecation_app}/tests.py
  158. 0 tests/{test_runner/invalid_app → test_runner_invalid_app}/__init__.py
  159. 0 tests/test_runner_invalid_app/models/__init__.py
  160. 0 tests/{test_runner/invalid_app → test_runner_invalid_app}/tests/__init__.py
  161. +0 −34 tests/utils_tests/tests.py
  162. +0 −6 tests/validation/tests.py
View
@@ -5,4 +5,4 @@ MANIFEST
dist/
docs/_build/
tests/coverage_html/
-tests/.coverage
+tests/.coverage
View
@@ -36,6 +36,7 @@ The PRIMARY AUTHORS are (and/or have been):
* Preston Holmes
* Simon Charette
* Donald Stufft
+ * Marc Tamlyn
More information on the main contributors to Django can be found in
docs/internals/committers.txt.
@@ -121,6 +122,7 @@ answer newbie questions, and generally made Django that much better:
Chris Cahoon <chris.cahoon@gmail.com>
Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com>
Trevor Caira <trevor@caira.com>
+ Aaron Cannon <cannona@fireantproductions.com>
Brett Cannon <brett@python.org>
Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>
Jeremy Carbaugh <jcarbaugh@gmail.com>
@@ -539,7 +541,6 @@ answer newbie questions, and generally made Django that much better:
Aaron Swartz <http://www.aaronsw.com/>
Ville Säävuori <http://www.unessa.net/>
Mart Sõmermaa <http://mrts.pri.ee/>
- Marc Tamlyn
Christian Tanzer <tanzer@swing.co.at>
Tyler Tarabula <tyler.tarabula@gmail.com>
Tyson Tate <tyson@fallingbullets.com>
@@ -576,7 +576,7 @@
###########
# The name of the class to use to run the test suite
-TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
+TEST_RUNNER = 'django.test.runner.DiscoverRunner'
############
# FIXTURES #
@@ -37,7 +37,7 @@
HORIZONTAL, VERTICAL = 1, 2
# returns the <ul> class for a given radio_admin field
-get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
+get_ul_class = lambda x: 'radiolist%s' % (' inline' if x == HORIZONTAL else '')
class IncorrectLookupParameters(Exception):
@@ -189,7 +189,7 @@ def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
'class': get_ul_class(self.radio_fields[db_field.name]),
})
- kwargs['empty_label'] = db_field.blank and _('None') or None
+ kwargs['empty_label'] = _('None') if db_field.blank else None
queryset = self.get_field_queryset(db, db_field, request)
if queryset is not None:
@@ -1 +0,0 @@
-from .test_fields import TestFieldType
@@ -267,7 +267,7 @@ def model_detail(request, app_label, model_name):
return render_to_response('admin_doc/model_detail.html', {
'root_path': urlresolvers.reverse('admin:index'),
'name': '%s.%s' % (opts.app_label, opts.object_name),
- 'summary': _("Fields on %s objects") % opts.object_name,
+ 'summary': _("Attributes on %s objects") % opts.object_name,
'description': model.__doc__,
'fields': fields,
}, context_instance=RequestContext(request))
@@ -171,7 +171,7 @@ def __init__(self, request=None, *args, **kwargs):
# Set the label for the "username" field.
UserModel = get_user_model()
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
- if not self.fields['username'].label:
+ if self.fields['username'].label is None:
self.fields['username'].label = capfirst(self.username_field.verbose_name)
def clean(self):
@@ -9,7 +9,7 @@
from django.test.signals import setting_changed
from django.utils import importlib
from django.utils.datastructures import SortedDict
-from django.utils.encoding import force_bytes, force_str
+from django.utils.encoding import force_bytes, force_str, force_text
from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import (
pbkdf2, constant_time_compare, get_random_string)
@@ -263,13 +263,13 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher):
Secure password hashing using the bcrypt algorithm (recommended)
This is considered by many to be the most secure algorithm but you
- must first install the py-bcrypt library. Please be warned that
+ must first install the bcrypt library. Please be warned that
this library depends on native C code and might cause portability
issues.
"""
algorithm = "bcrypt_sha256"
digest = hashlib.sha256
- library = ("py-bcrypt", "bcrypt")
+ library = ("bcrypt", "bcrypt")
rounds = 12
def salt(self):
@@ -291,7 +291,7 @@ def encode(self, password, salt):
password = force_bytes(password)
data = bcrypt.hashpw(password, salt)
- return "%s$%s" % (self.algorithm, data)
+ return "%s$%s" % (self.algorithm, force_text(data))
def verify(self, password, encoded):
algorithm, data = encoded.split('$', 1)
@@ -307,6 +307,9 @@ def verify(self, password, encoded):
else:
password = force_bytes(password)
+ # Ensure that our data is a bytestring
+ data = force_bytes(data)
+
return constant_time_compare(data, bcrypt.hashpw(password, data))
def safe_summary(self, encoded):
@@ -326,7 +329,7 @@ class BCryptPasswordHasher(BCryptSHA256PasswordHasher):
Secure password hashing using the bcrypt algorithm
This is considered by many to be the most secure algorithm but you
- must first install the py-bcrypt library. Please be warned that
+ must first install the bcrypt library. Please be warned that
this library depends on native C code and might cause portability
issues.
@@ -1,16 +1 @@
-from django.contrib.auth.tests.test_custom_user import *
-from django.contrib.auth.tests.test_auth_backends import *
-from django.contrib.auth.tests.test_basic import *
-from django.contrib.auth.tests.test_context_processors import *
-from django.contrib.auth.tests.test_decorators import *
-from django.contrib.auth.tests.test_forms import *
-from django.contrib.auth.tests.test_remote_user import *
-from django.contrib.auth.tests.test_management import *
-from django.contrib.auth.tests.test_models import *
-from django.contrib.auth.tests.test_handlers import *
-from django.contrib.auth.tests.test_hashers import *
-from django.contrib.auth.tests.test_signals import *
-from django.contrib.auth.tests.test_tokens import *
-from django.contrib.auth.tests.test_views import *
-
# The password for the fixture data users is 'password'
@@ -1,6 +1,8 @@
from __future__ import unicode_literals
import os
+
+from django.contrib.auth import get_user_model
from django.contrib.auth.models import User
from django.contrib.auth.forms import (UserCreationForm, AuthenticationForm,
PasswordChangeForm, SetPasswordForm, UserChangeForm, PasswordResetForm,
@@ -13,6 +15,7 @@
from django.utils.encoding import force_text
from django.utils._os import upath
from django.utils import translation
+from django.utils.text import capfirst
from django.utils.translation import ugettext as _
@@ -146,6 +149,24 @@ class CustomAuthenticationForm(AuthenticationForm):
form = CustomAuthenticationForm()
self.assertEqual(form['username'].label, "Name")
+ def test_username_field_label_not_set(self):
+
+ class CustomAuthenticationForm(AuthenticationForm):
+ username = CharField()
+
+ form = CustomAuthenticationForm()
+ UserModel = get_user_model()
+ username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
+ self.assertEqual(form.fields['username'].label, capfirst(username_field.verbose_name))
+
+ def test_username_field_label_empty_string(self):
+
+ class CustomAuthenticationForm(AuthenticationForm):
+ username = CharField(label='')
+
+ form = CustomAuthenticationForm()
+ self.assertEqual(form.fields['username'].label, "")
+
@skipIfCustomUser
@override_settings(USE_TZ=False, PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -2,7 +2,7 @@
from django.contrib.auth.handlers.modwsgi import check_password, groups_for_user
from django.contrib.auth.models import User, Group
-from django.contrib.auth.tests import CustomUser
+from django.contrib.auth.tests.test_custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.test import TransactionTestCase
from django.test.utils import override_settings
@@ -92,7 +92,7 @@ def test_crypt(self):
self.assertFalse(check_password('lètmeiz', encoded))
self.assertEqual(identify_hasher(encoded).algorithm, "crypt")
- @skipUnless(bcrypt, "py-bcrypt not installed")
+ @skipUnless(bcrypt, "bcrypt not installed")
def test_bcrypt_sha256(self):
encoded = make_password('lètmein', hasher='bcrypt_sha256')
self.assertTrue(is_password_usable(encoded))
@@ -108,7 +108,7 @@ def test_bcrypt_sha256(self):
self.assertTrue(check_password(password, encoded))
self.assertFalse(check_password(password[:72], encoded))
- @skipUnless(bcrypt, "py-bcrypt not installed")
+ @skipUnless(bcrypt, "bcrypt not installed")
def test_bcrypt(self):
encoded = make_password('lètmein', hasher='bcrypt')
self.assertTrue(is_password_usable(encoded))
@@ -5,7 +5,7 @@
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
-from django.contrib.auth.tests import CustomUser
+from django.contrib.auth.tests.test_custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.core.management import call_command
from django.core.management.base import CommandError
@@ -37,7 +37,7 @@ def next_redirect(request, fallback, **get_kwargs):
else:
anchor = ''
- joiner = ('?' in next) and '&' or '?'
+ joiner = '&' if '?' in next else '?'
next += joiner + urlencode(get_kwargs) + anchor
return HttpResponseRedirect(next)
@@ -75,7 +75,7 @@ def shortcut(request, content_type_id, object_id):
# If all that malarkey found an object domain, use it. Otherwise, fall back
# to whatever get_absolute_url() returned.
if object_domain is not None:
- protocol = request.is_secure() and 'https' or 'http'
+ protocol = 'https' if request.is_secure() else 'http'
return http.HttpResponseRedirect('%s://%s%s'
% (protocol, object_domain, absurl))
else:
@@ -1,6 +0,0 @@
-from django.contrib.flatpages.tests.test_csrf import *
-from django.contrib.flatpages.tests.test_forms import *
-from django.contrib.flatpages.tests.test_models import *
-from django.contrib.flatpages.tests.test_middleware import *
-from django.contrib.flatpages.tests.test_templatetags import *
-from django.contrib.flatpages.tests.test_views import *
@@ -1,2 +0,0 @@
-from django.contrib.formtools.tests.tests import *
-from django.contrib.formtools.tests.wizard import *
@@ -5,7 +5,7 @@
from __future__ import absolute_import
from django.conf.urls import patterns, url
-from django.contrib.formtools.tests import TestFormPreview
+from django.contrib.formtools.tests.tests import TestFormPreview
from django.contrib.formtools.tests.forms import TestForm
@@ -17,7 +17,7 @@
class DummyRequest(http.HttpRequest):
def __init__(self, POST=None):
super(DummyRequest, self).__init__()
- self.method = POST and "POST" or "GET"
+ self.method = "POST" if POST else "GET"
if POST is not None:
self.POST.update(POST)
self.session = {}
@@ -44,6 +44,7 @@ class GeometryField(Field):
# The OpenGIS Geometry name.
geom_type = 'GEOMETRY'
+ form_class = forms.GeometryField
# Geodetic units.
geodetic_units = ('Decimal Degree', 'degree')
@@ -201,11 +202,14 @@ def db_type(self, connection):
return connection.ops.geo_db_type(self)
def formfield(self, **kwargs):
- defaults = {'form_class' : forms.GeometryField,
+ defaults = {'form_class' : self.form_class,
'geom_type' : self.geom_type,
'srid' : self.srid,
}
defaults.update(kwargs)
+ if (self.dim > 2 and not 'widget' in kwargs and
+ not getattr(defaults['form_class'].widget, 'supports_3d', False)):
+ defaults['widget'] = forms.Textarea
return super(GeometryField, self).formfield(**defaults)
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
@@ -267,28 +271,35 @@ def get_placeholder(self, value, connection):
# The OpenGIS Geometry Type Fields
class PointField(GeometryField):
geom_type = 'POINT'
+ form_class = forms.PointField
description = _("Point")
class LineStringField(GeometryField):
geom_type = 'LINESTRING'
+ form_class = forms.LineStringField
description = _("Line string")
class PolygonField(GeometryField):
geom_type = 'POLYGON'
+ form_class = forms.PolygonField
description = _("Polygon")
class MultiPointField(GeometryField):
geom_type = 'MULTIPOINT'
+ form_class = forms.MultiPointField
description = _("Multi-point")
class MultiLineStringField(GeometryField):
geom_type = 'MULTILINESTRING'
+ form_class = forms.MultiLineStringField
description = _("Multi-line string")
class MultiPolygonField(GeometryField):
geom_type = 'MULTIPOLYGON'
+ form_class = forms.MultiPolygonField
description = _("Multi polygon")
class GeometryCollectionField(GeometryField):
geom_type = 'GEOMETRYCOLLECTION'
+ form_class = forms.GeometryCollectionField
description = _("Geometry collection")
@@ -121,7 +121,7 @@ def get_default_columns(self, with_aliases=False, col_aliases=None,
"""
result = []
if opts is None:
- opts = self.query.model._meta
+ opts = self.query.get_meta()
aliases = set()
only_load = self.deferred_to_columns()
seen = self.query.included_inherited_models.copy()
@@ -247,7 +247,7 @@ def _field_column(self, field, table_alias=None, column=None):
used. If `column` is specified, it will be used instead of the value
in `field.column`.
"""
- if table_alias is None: table_alias = self.query.model._meta.db_table
+ if table_alias is None: table_alias = self.query.get_meta().db_table
return "%s.%s" % (self.quote_name_unless_alias(table_alias),
self.connection.ops.quote_name(column or field.column))
@@ -1,2 +1,5 @@
from django.forms import *
-from django.contrib.gis.forms.fields import GeometryField
+from .fields import (GeometryField, GeometryCollectionField, PointField,
+ MultiPointField, LineStringField, MultiLineStringField, PolygonField,
+ MultiPolygonField)
+from .widgets import BaseGeometryWidget, OpenLayersWidget, OSMWidget
Oops, something went wrong.

0 comments on commit b31eea0

Please sign in to comment.