Skip to content

Commit

Permalink
make integer IntegerIdentifier pickleable so FK reverse accessor works
Browse files Browse the repository at this point in the history
Merge remote-tracking branch 'origin/test-fk-reverse-relation' into
develop
  • Loading branch information
thenewguy committed Apr 19, 2018
2 parents 09b5a51 + 534051d commit 5064f4a
Show file tree
Hide file tree
Showing 13 changed files with 218 additions and 34 deletions.
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ db.sqlite3
/dist
*.egg-info
.eggs
.coverage
.coverage
/.project
/.pydevproject
/vagrant/.vagrant
/.tox
16 changes: 16 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,19 @@ django-randomfields

.. image:: https://badge.fury.io/py/django-randomfields.svg
:target: http://badge.fury.io/py/django-randomfields

============
testing
============

cd vagrant/
vagrant up
vagrant ssh
cd /vagrant/

# note we move TOX_WORK_DIR outside of the vagrant synced folder to increase performance
TOX_WORK_DIR=/tmp tox -vv

-- or test one environment and skip the coverage report --

SUPPRESS_COVERAGE_REPORT="--suppress-coverage-report" TOX_WORK_DIR="/tmp" tox -vv -e py36-django-20
4 changes: 3 additions & 1 deletion randomfields/checks.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django import VERSION as DJANGO_VERSION

DJANGO_VERSION_17 = DJANGO_VERSION < (1, 8) and (1, 7) <= DJANGO_VERSION
DJANGO_VERSION_LT_18 = DJANGO_VERSION < (1, 8)
DJANGO_VERSION_LT_18 = DJANGO_VERSION < (1, 8)
DJANGO_VERSION_LT_19 = DJANGO_VERSION < (1, 9)
DJANGO_VERSION_LT_20 = DJANGO_VERSION < (2, 0)
8 changes: 8 additions & 0 deletions randomfields/models/fields/integer/identifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ def __new__(cls, value, possibilities, lower_bound, upper_bound):

return self

def __getnewargs__(self):
return (
self.db_value,
self.possibilities,
self.lower_bound,
self.upper_bound,
)

def __int__(self):
return self.db_value

Expand Down
14 changes: 7 additions & 7 deletions randomfields/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,29 +33,29 @@ class TestIdentifierData(models.Model):
data = RandomIntegerIdentifierField()

class TestIdentifierO2OValue(models.Model):
id = models.OneToOneField(TestIdentifierValue, primary_key=True, editable=True)
id = models.OneToOneField(TestIdentifierValue, on_delete=models.CASCADE, primary_key=True, editable=True)

class TestIdentifierFKValue(models.Model):
data = models.ForeignKey(TestIdentifierValue)
data = models.ForeignKey(TestIdentifierValue, on_delete=models.CASCADE)

class TestIdentifierM2MValue(models.Model):
data = models.ManyToManyField(TestIdentifierValue, blank=True)

class TestIdentifierAllValue(models.Model):
o2o = models.OneToOneField(TestIdentifierValue, related_name='+')
fk = models.ForeignKey(TestIdentifierValue, related_name='+')
o2o = models.OneToOneField(TestIdentifierValue, on_delete=models.CASCADE, related_name='+')
fk = models.ForeignKey(TestIdentifierValue, on_delete=models.CASCADE, related_name='+')
m2m = models.ManyToManyField(TestIdentifierValue, blank=True, related_name=unique_related_name())

class TestIdentifierM2MO2OPKValue(models.Model):
id = models.OneToOneField(TestIdentifierValue, primary_key=True, editable=True, related_name=unique_related_name())
id = models.OneToOneField(TestIdentifierValue, on_delete=models.CASCADE, primary_key=True, editable=True, related_name=unique_related_name())
m2m = models.ManyToManyField(TestIdentifierValue, blank=True, related_name=unique_related_name())

class TestIdentifierM2MO2OValue(models.Model):
o2o = models.OneToOneField(TestIdentifierValue, related_name='+')
o2o = models.OneToOneField(TestIdentifierValue, on_delete=models.CASCADE, related_name='+')
m2m = models.ManyToManyField(TestIdentifierValue, blank=True, related_name=unique_related_name())

class TestIdentifierM2MFKValue(models.Model):
fk = models.ForeignKey(TestIdentifierValue, related_name='+')
fk = models.ForeignKey(TestIdentifierValue, on_delete=models.CASCADE, related_name='+')
m2m = models.ManyToManyField(TestIdentifierValue, blank=True, related_name='+')

class TestMaskedAttrDetection(models.Model):
Expand Down
45 changes: 45 additions & 0 deletions randomfields/tests/test_identifier_fk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import copy
import pickle

from django.test import TestCase
from randomfields.checks import DJANGO_VERSION_17
from randomfields.tests.models import TestIdentifierValue, TestIdentifierFKValue
from randomfields.models.fields.integer import IntegerIdentifier
from unittest import skipIf


@skipIf(DJANGO_VERSION_17, "Not supported on Django 17")
class IdentifierFKTests(TestCase):
def test_reverse_accessor(self):
obj = TestIdentifierValue.objects.create()
rel = TestIdentifierFKValue.objects.create(data=obj)

instance = TestIdentifierFKValue.objects.get(pk=rel.pk)

# ensure we don't raise a type error when accessing the reverse relation
# encountering the following exception when we do at the moment:
# TypeError: __new__() missing 3 required positional arguments: 'possibilities', 'lower_bound', and 'upper_bound'
# this issue began to occur in Django 2.0 and seems to be an issue
# with copy/deepcopy and the IntegerIdentifier class
instance.data

def test_integer_identifier_copy(self):
value = IntegerIdentifier(1, 3, -1, 1)
value_copy = copy.copy(value)
self.assertEqual(value, value_copy)

def test_integer_identifier_deepcopy(self):
value = IntegerIdentifier(1, 3, -1, 1)
value_deepcopy = copy.deepcopy(value)
self.assertEqual(value, value_deepcopy)

def test_integer_identifier_pickle_dumps(self):
value = IntegerIdentifier(1, 3, -1, 1)
pickle.dumps(value)

def test_integer_identifier_pickle_loads(self):
value = IntegerIdentifier(1, 3, -1, 1)
value_pickled = pickle.dumps(value)
value_unpickled = pickle.loads(value_pickled)

self.assertEqual(value, value_unpickled)
17 changes: 11 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@
class RunTestsCommand(SetuptoolsTestCommand):
user_options = [
('only=', 'o', 'Only run the specified tests'),
('level=', 'l', 'Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
('level=', 'l', 'Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output'),
('suppress-coverage-report', None, 'Suppress coverage report'),
]
def initialize_options(self):
SetuptoolsTestCommand.initialize_options(self)
self.test_suite = "override"
self.only = ""
self.level = "1"
self.suppress_coverage_report = None

def finalize_options(self):
SetuptoolsTestCommand.finalize_options(self)
self.test_suite = None
self.level = int(self.level)
self.suppress_coverage_report = self.suppress_coverage_report is not None

def run(self):
SetuptoolsTestCommand.run(self)
Expand All @@ -29,16 +32,18 @@ def run_tests(self):
import subprocess
import sys
import time

owd = os.path.abspath(os.getcwd())
nwd = os.path.abspath(os.path.dirname(__file__))
os.chdir(nwd)
tests = split(self.only)
if not tests:
tests.extend([nwd, os.path.abspath('test_project')])
errno = coverage.cmdline.main(['run', os.path.abspath('test_project/manage.py'), 'test', '--verbosity=%d' % self.level] + tests)
coverage.cmdline.main(['report', '-m'])

if not self.suppress_coverage_report:
coverage.cmdline.main(['report', '-m'])

if None not in [os.getenv("TRAVIS", None), os.getenv("TRAVIS_JOB_ID", None), os.getenv("TRAVIS_BRANCH", None)]:
env = os.environ.copy()
env["PYTHONPATH"] = os.pathsep.join(sys.path)
Expand All @@ -52,9 +57,9 @@ def run_tests(self):
time.sleep(seconds)
else:
print("coveralls failed.")

os.chdir(owd)

raise SystemExit(errno)

tests_require = ['coverage', 'beautifulsoup4', 'html5lib', 'coveralls']
Expand All @@ -63,7 +68,7 @@ def run_tests(self):

setup(
name = "django-randomfields",
version = "0.1.7",
version = "0.1.8",
description = "Random fields for django models",
url = "https://github.com/thenewguy/django-randomfields",
cmdclass={'test': RunTestsCommand},
Expand Down
32 changes: 22 additions & 10 deletions test_project/test_project/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,28 @@
'testadmin',
)

MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 'django.middleware.security.SecurityMiddleware',# Raises 'ImportError: No module named security' on DJ1.7 (obviously, since added in 1.8)
)
from randomfields.checks import DJANGO_VERSION_LT_20

if DJANGO_VERSION_LT_20:
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 'django.middleware.security.SecurityMiddleware',# Raises 'ImportError: No module named security' on DJ1.7 (obviously, since added in 1.8)
)
else:
MIDDLEWARE = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

ROOT_URLCONF = 'test_project.urls'

Expand Down
8 changes: 7 additions & 1 deletion test_project/test_project/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
"""
from django.conf.urls import include, url
from django.contrib import admin
from randomfields.checks import DJANGO_VERSION_LT_19

def include_compat(included_urls):
if DJANGO_VERSION_LT_19:
included_urls = include(included_urls)
return included_urls

urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^admin/', include_compat(admin.site.urls)),
]
13 changes: 7 additions & 6 deletions test_project/testadmin/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
try:
from django.urls import reverse
except ImportError:
# backwards compatibility Django < 2.0
# https://docs.djangoproject.com/en/2.0/releases/2.0/#features-removed-in-2-0
from django.core.urlresolvers import reverse
from django.test import TestCase
from randomfields.checks import DJANGO_VERSION_17
from randomfields.models.fields import RandomCharField, RandomBigIntegerField
Expand Down Expand Up @@ -79,11 +84,7 @@ def _test_identifier_selected_in_html(self, url, value):
options = soup.find_all('option', value=value)
self.assertTrue(options, "No options found with value '%s'. All options: %s" % (value, soup.find_all('option')))
for option in options:
try:
selected = option["selected"]
except KeyError:
selected = "Key error. Option html: %s" % option
self.assertEqual(selected, "selected")
self.assertTrue(option.has_attr("selected"), "Key error. Option not selected! Option html: %s" % option)

def test_identifier_o2o_html(self):
obj = TestIdentifierValue.objects.create()
Expand Down
9 changes: 7 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
[tox]
args_are_paths = false
envlist =
{py27,py34}-django-{17,18,19,110,master}
{py27,py34}-django-{17,18,19,110,111}
{py33}-django-{17,18}
{py35}-django-{19,110,master}
{py34}-django-{20}
{py35}-django-{19,110,111,20,master}
{py36}-django-{111,20,master}

[testenv]
passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
Expand All @@ -12,6 +14,7 @@ basepython =
py33: python3.3
py34: python3.4
py35: python3.5
py36: python3.6
usedevelop = true
pip_pre = true
deps =
Expand All @@ -20,6 +23,8 @@ deps =
django-18: Django>=1.8,<1.9
django-19: Django>=1.9,<1.10
django-110: Django>=1.10,<1.11
django-111: Django>=1.11,<2
django-20: Django>=2.0,<2.1
django-master: https://github.com/django/django/archive/master.tar.gz
commands =
python --version
Expand Down
62 changes: 62 additions & 0 deletions vagrant/Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
host = RbConfig::CONFIG['host_os']
HOST_IS_MAC = host =~ /darwin/
HOST_IS_LINUX = host =~ /linux/
HOST_IS_WINDOWS = host =~ /mswin|mingw|cygwin/

if HOST_IS_MAC
HOST_MEM = `sysctl -n hw.memsize`.to_i / 1024 / 1024
HOST_CPUS = `sysctl -n hw.ncpu`.to_i
elsif HOST_IS_LINUX
HOST_MEM = `grep 'MemTotal' /proc/meminfo | sed -e 's/MemTotal://' -e 's/ kB//'`.to_i / 1024
HOST_CPUS = `nproc`.to_i
elsif HOST_IS_WINDOWS
HOST_MEM = `wmic computersystem Get TotalPhysicalMemory`.split[1].to_i / 1024 / 1024
HOST_CPUS = `wmic cpu Get NumberOfCores`.split[1].to_i
end

Vagrant.configure("2") do |config|
config.vm.boot_timeout = 600
config.vm.box = "bento/ubuntu-14.04"
config.vm.box_url = "https://vagrantcloud.com/bento/boxes/ubuntu-14.04/versions/201802.02.0/providers/virtualbox.box"

cpus = HOST_CPUS
if 7000 < HOST_MEM
mem = 4096
else
mem = 2048
end

config.vm.provider "virtualbox" do |v|
v.name = "django-randomfields"
v.memory = mem
v.cpus = cpus
if cpus > 1
v.customize ["modifyvm", :id, "--ioapic", "on"]
end
v.customize ["modifyvm", :id, "--cpuexecutioncap", "75"]
end


config.vm.provision :shell, path: "provision.sh"
config.vm.synced_folder ".", "/vagrant", disabled: true
config.vm.synced_folder "../", "/vagrant"


# forward ports as listed in vagrant/vagrant/rebuild.sh
#
##
##
## THIS ALLOWS THE WEB BROWSER ON THE HOST MACHINE
## TO COMMUNICATE VIA '127.0.0.1' or 'localhost'
## i.e. `curl -i http://127.0.0.1:8080/`
##
## THIS ALSO ALLOWS NETWORKED MACHINES TO ACCESS FORWARDED
## PORTS VIA THE HOST
## i.e. `curl -i http://host-ip-or-fqdn:8080/
##
##

# responder http (use 8080 to avoid sudo requirement)
config.vm.network "forwarded_port", guest: 80, host: 8080

end
18 changes: 18 additions & 0 deletions vagrant/provision.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
set -o errexit
set -o pipefail
set -o nounset
shopt -s failglob
set -o xtrace

export DEBIAN_FRONTEND=noninteractive

add-apt-repository ppa:deadsnakes/ppa

apt-get update

apt-get install -y git python3.5 python3.6

curl -O https://bootstrap.pypa.io/get-pip.py
python get-pip.py

pip install tox

0 comments on commit 5064f4a

Please sign in to comment.