Skip to content

Commit

Permalink
Merge pull request #104 from theatlantic/pr/django-1.11-support
Browse files Browse the repository at this point in the history
Support Django up to 1.11, fix DeprecationWarnings, fixes #89
  • Loading branch information
gregmuellegger committed Jun 8, 2017
2 parents 7d0c3bb + 0555463 commit 327ae68
Show file tree
Hide file tree
Showing 19 changed files with 246 additions and 128 deletions.
71 changes: 38 additions & 33 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,43 @@ language: python
sudo: false
matrix:
include:
- python: 3.5
env:
- TOX_ENV=py35-18
- python: 3.5
env:
- TOX_ENV=py35-18-postgres
- python: 3.5
env:
- TOX_ENV=py35-19
env:
- TOX_ENV=py26-15
- TOX_ENV=py26-16
- TOX_ENV=py27-15
- TOX_ENV=py27-16
- TOX_ENV=py27-17
- TOX_ENV=py27-18
- TOX_ENV=py27-18-postgres
- TOX_ENV=py27-18-mysql
- TOX_ENV=py27-19
- TOX_ENV=pypy-15
- TOX_ENV=pypy-16
- TOX_ENV=pypy-17
- TOX_ENV=pypy-18
- TOX_ENV=pypy-18-mysql
- TOX_ENV=pypy-19
- TOX_ENV=py33-16
- TOX_ENV=py33-17
- TOX_ENV=py33-18
- TOX_ENV=py34-16
- TOX_ENV=py34-17
- TOX_ENV=py34-18
- TOX_ENV=py34-19
- { python: 2.6, env: TOXENV=py26-15 }
- { python: 2.6, env: TOXENV=py26-16 }
- { python: 2.7, env: TOXENV=py27-15 }
- { python: 2.7, env: TOXENV=py27-16 }
- { python: 2.7, env: TOXENV=py27-17 }
- { python: 2.7, env: TOXENV=py27-18 }
- { python: 2.7, env: TOXENV=py27-18-postgres }
- { python: 2.7, env: TOXENV=py27-18-mysql }
- { python: 2.7, env: TOXENV=py27-19 }
- { python: 2.7, env: TOXENV=py27-110 }
- { python: 2.7, env: TOXENV=py27-111 }
- { python: pypy, env: TOXENV=pypy-15 }
- { python: pypy, env: TOXENV=pypy-16 }
- { python: pypy, env: TOXENV=pypy-17 }
- { python: pypy, env: TOXENV=pypy-18 }
- { python: pypy, env: TOXENV=pypy-18-mysql }
- { python: pypy, env: TOXENV=pypy-19 }
- { python: pypy, env: TOXENV=pypy-110 }
- { python: pypy, env: TOXENV=pypy-111 }
- { python: 3.3, env: TOXENV=py33-16 }
- { python: 3.3, env: TOXENV=py33-17 }
- { python: 3.3, env: TOXENV=py33-18 }
- { python: 3.4, env: TOXENV=py34-16 }
- { python: 3.4, env: TOXENV=py34-17 }
- { python: 3.4, env: TOXENV=py34-18 }
- { python: 3.4, env: TOXENV=py34-19 }
- { python: 3.4, env: TOXENV=py34-110 }
- { python: 3.4, env: TOXENV=py34-111 }
- { python: 3.5, env: TOXENV=py35-18 }
- { python: 3.5, env: TOXENV=py35-18-postgres }
- { python: 3.5, env: TOXENV=py35-19 }
- { python: 3.5, env: TOXENV=py35-110 }
- { python: 3.5, env: TOXENV=py35-111 }
- { python: 3.6, env: TOXENV=py36-111 }
- { python: 3.6, env: TOXENV=py36-master-postgres }
allow_failures:
- env: TOXENV=py36-master-postgres
before_install:
- export PIP_USE_MIRRORS=true
- export DJANGO_SETTINGS_MODULE=test_settings
Expand All @@ -47,7 +52,7 @@ before_install:
install:
- pip install tox
script:
- tox -e $TOX_ENV -v
- tox -v
deploy:
provider: pypi
user: gremu
Expand Down
14 changes: 10 additions & 4 deletions example/testapp/models.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
# -*- coding: utf-8 -*-
from django.db import models
try:
from django.urls import reverse
except ImportError:
from django.core.urlresolvers import reverse
from django.utils.encoding import python_2_unicode_compatible
from sortedm2m.fields import SortedManyToManyField


@python_2_unicode_compatible
class Car(models.Model):
plate = models.CharField(max_length=50)

def __unicode__(self):
def __str__(self):
return self.plate


@python_2_unicode_compatible
class ParkingArea(models.Model):
name = models.CharField(max_length=50)
cars = SortedManyToManyField(Car)

def __unicode__(self):
def __str__(self):
return self.name

@models.permalink
def get_absolute_url(self):
return 'parkingarea', (self.pk,), {}
return reverse('parkingarea', (self.pk,))
19 changes: 13 additions & 6 deletions example/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# -*- coding: utf-8 -*-
from django.conf.urls import include, patterns, url
import django
from django.conf.urls import include, url
from django.contrib import admin
from django.conf import settings
from django.http import HttpResponse
import django.views.static

import example.testapp.views

admin.autodiscover()

Expand All @@ -16,9 +19,13 @@ def handle500(request):
handler500 = 'example.urls.handle500'


urlpatterns = patterns('',
url(r'^media/(.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT}),
url(r'^admin/', include(admin.site.urls), name="admin"),
url(r'^parkingarea/(?P<pk>\d+)/$', 'example.testapp.views.parkingarea_update', name='parkingarea'),
if django.VERSION < (1, 9):
urlpatterns = [url(r'^admin/', include(admin.site.urls), name="admin")]
else:
urlpatterns = [url(r'^admin/', admin.site.urls, name="admin")]

urlpatterns += [
url(r'^media/(.*)$', django.views.static.serve, {'document_root': settings.MEDIA_ROOT}),
url(r'^parkingarea/(?P<pk>\d+)/$', example.testapp.views.parkingarea_update, name='parkingarea'),
url(r'^', include('django.contrib.staticfiles.urls')),
)
]
3 changes: 0 additions & 3 deletions requirements/tests.txt

This file was deleted.

16 changes: 15 additions & 1 deletion runtests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os, sys
import os, sys, warnings


parent = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -32,6 +32,20 @@


def runtests(*args):
if django.VERSION > (1, 8):
warnings.simplefilter("error", Warning)
warnings.filterwarnings("ignore", module="distutils")
try:
warnings.filterwarnings("ignore", category=ResourceWarning)
except NameError:
pass
warnings.filterwarnings("ignore", "invalid escape sequence", DeprecationWarning)
# Ignore a python 3.6 DeprecationWarning in ModelBase.__new__ that isn't
# fixed in Django 1.x
if sys.version_info > (3, 6) and django.VERSION < (2,):
warnings.filterwarnings(
"ignore", "__class__ not set defining", DeprecationWarning)

test_apps = list(args or default_test_apps)
execute_from_command_line([sys.argv[0], 'test', '--verbosity=1'] + test_apps)

Expand Down
42 changes: 39 additions & 3 deletions sortedm2m/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@
else:
from django.db.models import get_model

from django.db import models

try:
from django.db.models.fields.related_descriptors import create_forward_many_to_many_manager
except ImportError:
# Django <= 1.8 support.
from django.db.models.fields.related import create_many_related_manager as create_forward_many_to_many_manager

try:
from django.db.models.fields.related import lazy_related_operation
except ImportError:
lazy_related_operation = None
from django.db.models.fields.related import add_lazy_relation as _add_lazy_relation
else:
_add_lazy_relation = None


def get_model_name(model):
# Django 1.5 support.
Expand All @@ -32,9 +41,11 @@ def get_foreignkey_field_kwargs(field):
if django.VERSION < (1, 6):
return {}
else:
return dict(
db_tablespace=field.db_tablespace,
db_constraint=field.rel.db_constraint)
return {
'db_tablespace': field.db_tablespace,
'db_constraint': get_rel(field).db_constraint,
'on_delete': models.CASCADE,
}


def get_field(model, field_name):
Expand All @@ -56,3 +67,28 @@ def allow_migrate_model(self, connection_alias, model):
return self.allowed_to_migrate(connection_alias, model)
else:
return self.allow_migrate_model(connection_alias, model)


def get_rel(f):
if django.VERSION > (1, 9):
return f.remote_field
else:
return f.rel

def get_rel_to(f):
rel = get_rel(f)
if django.VERSION > (1, 9):
return rel.model
else:
return rel.to


def add_lazy_relation(cls, field, relation, operation):
if _add_lazy_relation is not None:
return _add_lazy_relation(cls, field, relation, operation)

# Rearrange args for new Apps.lazy_model_operation
def function(local, related, field):
return operation(field, related, local)

lazy_related_operation(function, cls, relation, field=field)
53 changes: 27 additions & 26 deletions sortedm2m/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@
from django.db import transaction
from django.db import models
from django.db.models import signals
from django.db.models.fields.related import add_lazy_relation
from django.db.models.fields.related import ManyToManyField as _ManyToManyField
from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
from django.utils import six
from django.utils.functional import cached_property, curry

from .compat import create_forward_many_to_many_manager
from .compat import get_foreignkey_field_kwargs
from .compat import get_model_name
from .compat import get_model_name, get_rel, get_rel_to, add_lazy_relation
from .forms import SortedMultipleChoiceField

SORT_VALUE_FIELD_NAME = 'sort_value'
Expand Down Expand Up @@ -203,9 +202,8 @@ class SortedManyToManyDescriptor(ReverseManyRelatedObjectsDescriptor):
@cached_property
def related_manager_cls(self):
return create_sorted_many_related_manager(
self.field.rel.to._default_manager.__class__,
self.field.rel
)
get_rel_to(self.field)._default_manager.__class__,
get_rel(self.field))


class SortedManyToManyField(_ManyToManyField):
Expand Down Expand Up @@ -247,16 +245,18 @@ def contribute_to_class(self, cls, name, **kwargs):
# specify *what* on my non-reversible relation?!"), so we set it up
# automatically. The funky name reduces the chance of an accidental
# clash.
if self.rel.symmetrical and (self.rel.to == "self" or self.rel.to == cls._meta.object_name):
self.rel.related_name = "%s_rel_+" % name
rel = get_rel(self)
rel_to = get_rel_to(self)
if rel.symmetrical and (rel_to == "self" or rel_to == cls._meta.object_name):
rel.related_name = "%s_rel_+" % name

super(_ManyToManyField, self).contribute_to_class(cls, name, **kwargs)

# The intermediate m2m model is not auto created if:
# 1) There is a manually specified intermediate, or
# 2) The class owning the m2m field is abstract.
if not self.rel.through and not cls._meta.abstract:
self.rel.through = self.create_intermediate_model(cls)
if not rel.through and not cls._meta.abstract:
rel.through = self.create_intermediate_model(cls)

# Add the descriptor for the m2m relation
setattr(cls, self.name, SortedManyToManyDescriptor(self))
Expand All @@ -266,16 +266,16 @@ def contribute_to_class(self, cls, name, **kwargs):

# Populate some necessary rel arguments so that cross-app relations
# work correctly.
if isinstance(self.rel.through, six.string_types):
if isinstance(rel.through, six.string_types):
def resolve_through_model(field, model, cls):
field.rel.through = model
add_lazy_relation(cls, self, self.rel.through, resolve_through_model)
get_rel(field).through = model
add_lazy_relation(cls, self, rel.through, resolve_through_model)

if hasattr(cls._meta, 'duplicate_targets'): # Django<1.5
if isinstance(self.rel.to, six.string_types):
target = self.rel.to
if isinstance(rel_to, six.string_types):
target = rel_to
else:
target = self.rel.to._meta.db_table
target = rel_to._meta.db_table
cls._meta.duplicate_targets[self.column] = (target, "m2m")

def get_internal_type(self):
Expand All @@ -299,11 +299,11 @@ def get_intermediate_model_meta_class(self, klass, from_field_name,
to_field_name,
sort_value_field_name):
managed = True
to_model = self.rel.to
if isinstance(self.rel.to, six.string_types):
if self.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
to_model = get_rel_to(self)
if isinstance(to_model, six.string_types):
if to_model != RECURSIVE_RELATIONSHIP_CONSTANT:
def set_managed(field, model, cls):
field.rel.through._meta.managed = model._meta.managed or cls._meta.managed
get_rel(field).through._meta.managed = model._meta.managed or cls._meta.managed
add_lazy_relation(klass, self, to_model, set_managed)
else:
managed = klass._meta.managed
Expand All @@ -329,15 +329,16 @@ def set_managed(field, model, cls):
return type(str('Meta'), (object,), options)

def get_rel_to_model_and_object_name(self, klass):
if isinstance(self.rel.to, six.string_types):
if self.rel.to != RECURSIVE_RELATIONSHIP_CONSTANT:
to_model = self.rel.to
rel_to = get_rel_to(self)
if isinstance(rel_to, six.string_types):
if rel_to != RECURSIVE_RELATIONSHIP_CONSTANT:
to_model = rel_to
to_object_name = to_model.split('.')[-1]
else:
to_model = klass
to_object_name = to_model._meta.object_name
else:
to_model = self.rel.to
to_model = rel_to
to_object_name = to_model._meta.object_name
return to_model, to_object_name

Expand All @@ -351,7 +352,7 @@ def get_intermediate_model_from_field(self, klass):

to_model, to_object_name = self.get_rel_to_model_and_object_name(klass)

if self.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to_object_name == klass._meta.object_name:
if get_rel_to(self) == RECURSIVE_RELATIONSHIP_CONSTANT or to_object_name == klass._meta.object_name:
field_name = 'from_%s' % to_object_name.lower()
else:
field_name = get_model_name(klass)
Expand All @@ -365,7 +366,7 @@ def get_intermediate_model_to_field(self, klass):

to_model, to_object_name = self.get_rel_to_model_and_object_name(klass)

if self.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT or to_object_name == klass._meta.object_name:
if get_rel_to(self) == RECURSIVE_RELATIONSHIP_CONSTANT or to_object_name == klass._meta.object_name:
field_name = 'to_%s' % to_object_name.lower()
else:
field_name = to_object_name.lower()
Expand Down Expand Up @@ -456,7 +457,7 @@ def forwards_code(self):
"left_model_key": model_key(self.model),
"right_field": self.field.m2m_reverse_name()[:-3], # Remove the _id part
"right_column": self.field.m2m_reverse_name(),
"right_model_key": model_key(self.field.rel.to),
"right_model_key": model_key(get_rel_to(self.field)),
"sort_field": self.field.sort_value_field_name,
}
else:
Expand Down
Loading

0 comments on commit 327ae68

Please sign in to comment.