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 all 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)
54 changes: 54 additions & 0 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,60 @@ so the class Supplier could be wrote:
"type_tiers": RawFieldValue("S")
})

We also can refer by CompositeForeignKey in more flexible way using FunctionBasedFieldValue instead of RawFieldValue:

.. code:: python

from django.conf import global_settings
from django.utils import translation


class Supplier(models.Model):
company = models.IntegerField()
supplier_id = models.IntegerField()


class SupplierTranslations(models.Model):
master = models.ForeignKey(
Supplier,
on_delete=CASCADE,
related_name='translations',
null=True,
)
language_code = models.CharField(max_length=255, choices=global_settings.LANGUAGES)
name = models.CharField(max_length=255)
title = models.CharField(max_length=255)

class Meta:
unique_together = ('language_code', 'master')


active_translations = CompositeForeignKey(
SupplierTranslations,
on_delete=DO_NOTHING,
to_fields={
'master_id': 'id',
'language_code': FunctionBasedFieldValue(translation.get_language)
})


active_translations.contribute_to_class(Supplier, 'active_translations')


in this example, the Supplier Model joins with SupplierTranslations in current active language and
supplier_instance.active_translations.name will return different names depend on
which language was activated by translation.activate(..):

.. code:: python

translation.activate('en')
print Supplier.objects.get(id=1).active_translations.name
translation.activate('your_language_code')
print Supplier.objects.get(id=1).active_translations.name

output should be:
* 'en_language_name'
* 'your_language_name'

Treate specific values as None
------------------------------
Expand Down
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: 28 additions & 0 deletions testapp/fixtures/all_fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,33 @@
"company": 2,
"cod_rep": "DB"
}
},
{
"model": "testapp.MultiLangSupplier",
"pk": 1,
"fields": {
"company": 1,
"supplier_id": 1
}
},
{
"model": "testapp.SupplierTranslations",
"pk": 1,
"fields": {
"master": 1,
"language_code": "en",
"name": "en_name",
"title": "en_title"
}
},
{
"model": "testapp.SupplierTranslations",
"pk": 2,
"fields": {
"master": 1,
"language_code": "ru",
"name": "ru_name",
"title": "ru_title"
}
}
]
Loading