Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ticket #18620: Prefer current Site when checking M2M in "shortcut"/"view_on_site" redirect #203

Closed
wants to merge 7 commits into from
42 changes: 42 additions & 0 deletions django/contrib/contenttypes/tests.py
Expand Up @@ -8,6 +8,7 @@
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.http import HttpRequest, Http404 from django.http import HttpRequest, Http404
from django.test import TestCase from django.test import TestCase
from django.utils import unittest
from django.utils.encoding import smart_str from django.utils.encoding import smart_str




Expand Down Expand Up @@ -45,6 +46,13 @@ class FooWithBrokenAbsoluteUrl(FooWithoutUrl):
def get_absolute_url(self): def get_absolute_url(self):
return "/users/%s/" % self.unknown_field return "/users/%s/" % self.unknown_field


if Site._meta.installed:
class FooWithSiteM2MAndUrl(FooWithUrl):
"""
Fake model containing a `Site` many-to-many relationship.
"""
sites = models.ManyToManyField(Site)

class ContentTypesTests(TestCase): class ContentTypesTests(TestCase):


def setUp(self): def setUp(self):
Expand Down Expand Up @@ -260,6 +268,40 @@ def test_shortcut_view_with_broken_get_absolute_url(self):


self.assertRaises(AttributeError, shortcut, request, user_ct.id, obj.id) self.assertRaises(AttributeError, shortcut, request, user_ct.id, obj.id)


@unittest.skipIf(not Site._meta.installed,
"this tests against relationship behavior with the Sites framework")
def test_shortcut_view_m2m(self):
"""
Check that the shortcut view (used for the admin "view on site"
functionality) returns a complete URL regardless of whether the sites
framework is installed
"""
# Tests ticket #18620; prefer current_site rather than allowing
# M2M to select first domain via ABC order.
current_site = Site.objects.get_current() # example.com
other_site = Site(domain="a.example.com", name="a.example.com")
other_site.save()

# Build an object that belongs to both sites.
obj = FooWithSiteM2MAndUrl(name="john")
obj.save() # need saved object to set M2M relationship
obj.sites = [other_site, current_site]

# Check that shortcut view returns absolute URI for `current_site`
# and not `other_site`.
request = HttpRequest()
request.META = {
"SERVER_NAME": "Example.com",
"SERVER_PORT": "80",
}
user_ct = ContentType.objects.get_for_model(FooWithSiteM2MAndUrl)
response = shortcut(request, user_ct.id, obj.id)
self.assertEqual("http://%s/users/john/" % current_site.domain,
response._headers.get("location")[1])

obj.delete()
other_site.delete()

def test_missing_model(self): def test_missing_model(self):
""" """
Ensures that displaying content types in admin (or anywhere) doesn't Ensures that displaying content types in admin (or anywhere) doesn't
Expand Down
33 changes: 25 additions & 8 deletions django/contrib/contenttypes/views.py
Expand Up @@ -38,17 +38,37 @@ def shortcut(request, content_type_id, object_id):
# Otherwise, we need to introspect the object's relationships for a # Otherwise, we need to introspect the object's relationships for a
# relation to the Site object # relation to the Site object
object_domain = None object_domain = None
current_domain = None

# Get current site (if possible): this decides the preferred domain
# if this object has an M2M site field and provides a fallback if this
# object does not have a site FK or M2M field.
try:
current_domain = get_current_site(request).domain
except Site.DoesNotExist:
pass


if Site._meta.installed: if Site._meta.installed:
opts = obj._meta opts = obj._meta


# First, look for an many-to-many relationship to Site. # First, look for an many-to-many relationship to Site.
for field in opts.many_to_many: for field in opts.many_to_many:
if field.rel.to is Site: if field.rel.to is Site:
site_qs = getattr(obj, field.name).all()
try:
# If one of the sites this object belongs to is the current
# site, use it.
object_domain = site_qs.get(domain=current_domain).domain
except Site.DoesNotExist:
pass
if object_domain is not None:
break
try: try:
# Caveat: In the case of multiple related Sites, this just # Current site was not in the M2M relationship for this
# selects the *first* one, which is arbitrary. # object. Caveat: This just selects the *first* one, which
object_domain = getattr(obj, field.name).all()[0].domain # is arbitrary. (Generally based on Site model ordering,
# which is alphabetical by domain.)
object_domain = site_qs[0].domain
except IndexError: except IndexError:
pass pass
if object_domain is not None: if object_domain is not None:
Expand All @@ -65,12 +85,9 @@ def shortcut(request, content_type_id, object_id):
if object_domain is not None: if object_domain is not None:
break break


# Fall back to the current site (if possible). # Fall back to the current site (if possible) or None (which is fine).
if object_domain is None: if object_domain is None:
try: object_domain = current_domain
object_domain = get_current_site(request).domain
except Site.DoesNotExist:
pass


# If all that malarkey found an object domain, use it. Otherwise, fall back # If all that malarkey found an object domain, use it. Otherwise, fall back
# to whatever get_absolute_url() returned. # to whatever get_absolute_url() returned.
Expand Down
2 changes: 0 additions & 2 deletions django/db/models/__init__.py
Expand Up @@ -15,8 +15,6 @@
from django.db.models import signals from django.db.models import signals
from django.utils.decorators import wraps from django.utils.decorators import wraps


# Admin stages.
ADD, CHANGE, BOTH = 1, 2, 3


def permalink(func): def permalink(func):
""" """
Expand Down
50 changes: 0 additions & 50 deletions docs/ref/contrib/gis/install.txt
Expand Up @@ -1034,61 +1034,11 @@ Optional packages to consider:
do not plan on doing any database transformation of geometries to the do not plan on doing any database transformation of geometries to the
Google projection (900913). Google projection (900913).


.. _heron:

8.04 and lower
~~~~~~~~~~~~~~

The 8.04 (and lower) versions of Ubuntu use GEOS v2.2.3 in their binary packages,
which is incompatible with GeoDjango. Thus, do *not* use the binary packages
for GEOS or PostGIS and build some prerequisites from source, per the instructions
in this document; however, it is okay to use the PostgreSQL binary packages.

For more details, please see the Debian instructions for :ref:`etch` below.

.. _debian: .. _debian:


Debian Debian
------ ------


.. _etch:

4.0 (Etch)
^^^^^^^^^^

The situation here is the same as that of Ubuntu :ref:`heron` -- in other words,
some packages must be built from source to work properly with GeoDjango.

Binary packages
~~~~~~~~~~~~~~~
The following command will install acceptable binary packages, as well as
the development tools necessary to build the rest of the requirements:

.. code-block:: bash

$ sudo apt-get install binutils bzip2 gcc g++ flex make postgresql-8.1 \
postgresql-server-dev-8.1 python-ctypes python-psycopg2 python-setuptools

Required package information:

* ``binutils``: for ctypes to find libraries
* ``bzip2``: for decompressing the source packages
* ``gcc``, ``g++``, ``make``: GNU developer tools used to compile the libraries
* ``flex``: required to build PostGIS
* ``postgresql-8.1``
* ``postgresql-server-dev-8.1``: for ``pg_config``
* ``python-psycopg2``

Optional packages:

* ``libgeoip``: for :ref:`GeoIP <ref-geoip>` support

Source packages
~~~~~~~~~~~~~~~
You will still have to install :ref:`geosbuild`, :ref:`proj4`,
:ref:`postgis`, and :ref:`gdalbuild` from source. Please follow the
directions carefully.

.. _lenny: .. _lenny:


5.0 (Lenny) 5.0 (Lenny)
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/python3.txt
Expand Up @@ -181,7 +181,7 @@ xrange range xrange
=============================== ====================================== ====================== =============================== ====================================== ======================




Ouptut encoding now Unicode Output encoding now Unicode
=========================== ===========================


If you want to catch stdout/stderr output, the output content is UTF-8 encoded If you want to catch stdout/stderr output, the output content is UTF-8 encoded
Expand Down