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

Add Django 2 support, bump to v1.0.1 #13

Merged
merged 5 commits into from
Feb 26, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"

# Django versions for matrix
env:
- DJANGO_VERSION='>=1.8,<1.9'
- DJANGO_VERSION='>=1.9,<1.10'
- DJANGO_VERSION='>=1.10,<1.11'
- DJANGO_VERSION='>=1.11,<2.0'
- DJANGO_VERSION='>=2.0,<2.1'

# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install:
Expand All @@ -37,6 +39,16 @@ after_success:

matrix:
exclude:
- python: "2.7"
env: DJANGO_VERSION='>=2.0,<2.1'

- python: "3.6"
env: DJANGO_VERSION='>=1.8,<1.9'
- python: "3.6"
env: DJANGO_VERSION='>=1.9,<1.10'
- python: "3.6"
env: DJANGO_VERSION='>=1.10,<1.11'

- python: "3.5"
env: DJANGO_VERSION='>=1.8,<1.9'

Expand All @@ -54,3 +66,8 @@ matrix:
env: DJANGO_VERSION='>=1.11,<2.0'
- python: "3.3"
env: DJANGO_VERSION='>=1.11,<2.0'

- python: "3.2"
env: DJANGO_VERSION='>=2.0,<2.1'
- python: "3.3"
env: DJANGO_VERSION='>=2.0,<2.1'
2 changes: 1 addition & 1 deletion compositefk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# -*- coding: utf-8 -*-


__version__ = "1.0.0a10"
__version__ = "1.0.1"
29 changes: 29 additions & 0 deletions compositefk/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import django


def get_remote_field(field):
if django.VERSION < (1, 9):
return field.rel
else:
return field.remote_field


def get_cached_value(instance, descriptor, default=None):
if django.VERSION < (2, 0):
return getattr(instance, descriptor.cache_name, default)
else:
return descriptor.field.get_cached_value(instance, default=default)


def set_cached_value_by_descriptor(instance, descriptor, value):
if django.VERSION < (2, 0):
setattr(instance, descriptor.cache_name, value)
else:
descriptor.field.set_cached_value(instance, value)


def set_cached_value_by_field(instance, field, value):
if django.VERSION < (2, 0):
setattr(instance, field.get_cache_name(), value)
else:
field.set_cached_value(instance, value)
36 changes: 31 additions & 5 deletions compositefk/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@

from django.core import checks
from django.core.exceptions import FieldDoesNotExist
from django.db.models.expressions import Value
from django.db.models.fields import Field
from django.db.models.fields.related import ForeignObject
from django.db.models.sql.where import WhereNode, AND
from django.utils import six

from compositefk.compat import get_remote_field
from compositefk.related_descriptors import CompositeForwardManyToOneDescriptor

try:
Expand Down Expand Up @@ -239,8 +238,8 @@ def db_type(self, connection):
def db_parameters(self, connection):
return {"type": None, "check": None}

def contribute_to_class(self, cls, name, virtual_only=False):
super(ForeignObject, self).contribute_to_class(cls, name, virtual_only=virtual_only)
def contribute_to_class(self, cls, name, **kwargs):
super(ForeignObject, self).contribute_to_class(cls, name, **kwargs)
setattr(cls, self.name, CompositeForwardManyToOneDescriptor(self))

def get_instance_value_for_fields(self, instance, fields):
Expand Down Expand Up @@ -275,7 +274,7 @@ class CompositeOneToOneField(CompositeForeignKey):
def __init__(self, to, **kwargs):
kwargs['unique'] = True
super(CompositeOneToOneField, self).__init__(to, **kwargs)
self.rel.multiple = False
get_remote_field(self).multiple = False

def deconstruct(self):
name, path, args, kwargs = super(CompositeOneToOneField, self).deconstruct()
Expand Down Expand Up @@ -333,6 +332,33 @@ def get_lookup(self, main_field, for_remote, alias):
return lookup_class(for_remote.get_col(alias), self.value)


class FunctionBasedFieldValue(RawFieldValue):
def __init__(self, func):
self._func = func

def deconstruct(self):
module_name = self.__module__
name = self.__class__.__name__
return (
'%s.%s' % (module_name, name),
(self._func,),
{}
)

def __eq__(self, other):
if self.__class__ != other.__class__:
return False
return self._func == other._func

@property
def value(self):
return self._func()

@value.setter
def value(self):
pass


class LocalFieldValue(CompositePart):
"""
implicitly used, represent the value of a local field
Expand Down
26 changes: 10 additions & 16 deletions compositefk/related_descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from __future__ import unicode_literals, print_function, absolute_import
import logging


from compositefk.compat import (
get_remote_field,
set_cached_value_by_descriptor,
set_cached_value_by_field,
get_cached_value,
)

try:
from django.db.models.fields.related_descriptors import ForwardManyToOneDescriptor
Expand All @@ -30,30 +35,19 @@ def __set__(self, instance, value):
# populated the cache, then we don't care - we're only accessing
# the object to invalidate the accessor cache, so there's no
# need to populate the cache just to expire it again.
related = getattr(instance, self.cache_name, None)
related = get_cached_value(instance, self, None)

# If we've got an old related object, we need to clear out its
# cache. This cache also might not exist if the related object
# hasn't been accessed yet.
if related is not None:
try:
related_field = self.field.remote_field
except AttributeError:
related_field = self.field.rel
setattr(related, related_field.get_cache_name(), None)
related_field = get_remote_field(self.field)
set_cached_value_by_field(related, related_field, None)

# ##### only original part

for lh_field_name, none_value in self.field.nullable_fields.items():
setattr(instance, lh_field_name, none_value)


# Set the related instance cache used by __get__ to avoid a SQL query
# when accessing the attribute we just set.
setattr(instance, self.cache_name, value)

# If this is a one-to-one relation, set the reverse accessor cache on
# the related object to the current instance to avoid an extra SQL
# query if it's accessed later on.
if value is not None and not self.field.remote_field.multiple:
setattr(value, self.field.remote_field.get_cache_name(), instance)
set_cached_value_by_descriptor(instance, self, None)
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Django<1.9
Django<2.1
wheel
sphinx
sphinx
mock
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Operating System :: OS Independent',
'Topic :: Software Development :: Libraries',
'Topic :: Utilities',
Expand Down
28 changes: 26 additions & 2 deletions testapp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@
import django.db.models as models
from django.db.models.deletion import CASCADE

from compositefk.fields import CompositeForeignKey, RawFieldValue, LocalFieldValue, CompositeOneToOneField
from compositefk.fields import (
CompositeForeignKey,
RawFieldValue,
LocalFieldValue,
CompositeOneToOneField,
FunctionBasedFieldValue,
)

logger = logging.getLogger(__name__)
__author__ = 'darius.bernard'
Expand All @@ -29,12 +35,15 @@ class Meta(object):
]



class Representant(models.Model):
company = models.IntegerField()
cod_rep = models.CharField(max_length=2)


def get_local_type_tiers():
return 'C'


class Customer(models.Model):
company = models.IntegerField()
customer_id = models.IntegerField()
Expand All @@ -53,6 +62,21 @@ class Customer(models.Model):
("customer_id", -1)
])

local_address = CompositeForeignKey(
Address,
on_delete=CASCADE,
null=True,
to_fields=OrderedDict([
("company", LocalFieldValue("company")),
("tiers_id", "customer_id"),
("type_tiers", FunctionBasedFieldValue(get_local_type_tiers))
]),
null_if_equal=[ # if either of the fields company or customer is -1, ther can't have address
("company", -1),
("customer_id", -1)],
related_name='customer_local',
)

representant = CompositeForeignKey(Representant, on_delete=CASCADE, null=True, to_fields=[
"company",
"cod_rep",
Expand Down
27 changes: 25 additions & 2 deletions testapp/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

from __future__ import unicode_literals, print_function, absolute_import

try:
from unittest import mock
except ImportError:
from mock import mock

try:
from StringIO import StringIO
except ImportError:
Expand All @@ -16,7 +21,6 @@
from django.core.management.base import CommandError
from django.db.migrations.autodetector import MigrationAutodetector
from django.db.migrations.loader import MigrationLoader
from django.db.models.deletion import CASCADE

try:
from django.db.migrations.questioner import NonInteractiveMigrationQuestioner
Expand Down Expand Up @@ -158,6 +162,23 @@ def test_filtered_values(self):
address = Address.objects.get(pk=1)
self.assertEqual(customer.address, address)


class TestExtraFilterFunctionBasedValue(TestCase):
fixtures = ["all_fixtures.json"]

@mock.patch.object(Customer.local_address.field._raw_fields['type_tiers'], '_func')
def test_filtered_values(self, mock_get_local_type_tiers):
mock_get_local_type_tiers.return_value = 'C'
customer = Customer.objects.get(pk=1)
address = Address.objects.get(pk=1)
self.assertEqual(customer.local_address, address)

mock_get_local_type_tiers.return_value = 'S'
customer = Customer.objects.get(pk=1)
address = Address.objects.get(pk=2)
self.assertEqual(customer.local_address, address)


class TestNullIfEqual(TestCase):
fixtures = ["all_fixtures.json"]

Expand Down Expand Up @@ -236,6 +257,7 @@ def test_total_deconstruct(self):
migration_string = writer.as_string()
self.assertNotEqual(migration_string, "")


class TestOneToOne(TestCase):
fixtures = ["all_fixtures.json"]
def test_set(self):
Expand Down Expand Up @@ -310,6 +332,7 @@ def test_graph_data(self):
representant_2;
customer_1;
customer_1 -> address_1;
customer_1 -> address_1;
customer_1 -> representant_1;
customer_2;
customer_3;
Expand Down Expand Up @@ -360,4 +383,4 @@ def test_inital_value_to_related(self):

def test_inital_value_to_nullable(self):
c = Customer.objects.create(representant=None, name="test", customer_id=34, company=1)
self.assertEqual("", c.cod_rep)
self.assertEqual("", c.cod_rep)
2 changes: 1 addition & 1 deletion testsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
'testapp',
)

MIDDLEWARE_CLASSES = DEFAULT_SETTINGS.MIDDLEWARE_CLASSES
MIDDLEWARE_CLASSES = getattr(DEFAULT_SETTINGS, 'MIDDLEWARE_CLASSES', [])

TEMPLATES = [
{
Expand Down
5 changes: 4 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ envlist =
py{27,33,34}-django18
py{27,34,35}-django19
py{27,34,35}-django110
py{27,34,35}-django111
py{27,34,35,36}-django111
py{34,35,36}-django20


[testenv]
commands = coverage run --source=testapp,compositefk manage.py test
deps =
coverage
mock
django18: django >=1.8,<1.9
django19: django >=1.9,<1.10
django110: django >=1.10,<1.11
django111: django >=1.11,<2.0
django20: django >=2.0,<2.1