Skip to content

Commit

Permalink
Ported relations module
Browse files Browse the repository at this point in the history
  • Loading branch information
Relrin committed Oct 27, 2016
1 parent f68b1f2 commit de50197
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 2 deletions.
188 changes: 188 additions & 0 deletions aiorest_ws/db/orm/django/relations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# -*- coding: utf-8 -*-
"""
Module, which provide classes and function for related and nested field.
"""
from collections import OrderedDict

from aiorest_ws.db.orm import relations
from aiorest_ws.utils.fields import get_attribute, is_simple_callable

from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Manager
from django.db.models.query import QuerySet
from django.utils.encoding import smart_text


__all__ = (
'ManyRelatedField', 'RelatedField', 'StringRelatedField',
'PrimaryKeyRelatedField', 'HyperlinkedRelatedField',
'HyperlinkedIdentityField', 'SlugRelatedField'
)


class ManyRelatedField(relations.ManyRelatedField):
"""
Relationships with `many=True` transparently get coerced into instead being
a ManyRelatedField with a child relationship.
The `ManyRelatedField` class is responsible for handling iterating through
the values and passing each one to the child relationship.
This class is treated as private API.
You shouldn't generally need to be using this class directly yourself,
and should instead simply set 'many=True' on the relationship.
"""

def get_attribute(self, instance):
# Can't have any relationships if not created
if hasattr(instance, 'pk') and instance.pk is None:
return []

relationship = get_attribute(instance, self.source_attrs)
if hasattr(relationship, 'all'):
relationship.all()
return relationship


class RelatedField(relations.RelatedField):
many_related_field = ManyRelatedField

def get_queryset(self):
queryset = self.queryset
if isinstance(queryset, (QuerySet, Manager)):
# Ensure queryset is re-evaluated whenever used.
# Note that actually a `Manager` class may also be used as the
# queryset argument. This occurs on ModelSerializer fields,
# as it allows us to generate a more expressive 'repr' output
# for the field.
# Eg: 'MyRelationship(queryset=ExampleModel.objects.all())'
queryset = queryset.all()
return queryset

def get_attribute(self, instance):
if self.use_pk_only_optimization() and self.source_attrs:
# Optimized case, return a mock object only containing the
# pk attribute.
try:
instance = get_attribute(instance, self.source_attrs[:-1])
value = instance.serializable_value(self.source_attrs[-1])
if is_simple_callable(value):
# Handle edge case where the relationship `source` argument
# points to a `get_relationship()` method on the model
value = value().pk
return relations.PKOnlyObject(pk=value)
except AttributeError:
pass

# Standard case, return the object instance.
return get_attribute(instance, self.source_attrs)

def get_choices(self, cutoff=None):
queryset = self.get_queryset()
if queryset is None:
# Ensure that field.choices returns something sensible
# even when accessed with a read-only field.
return {}

if cutoff is not None:
queryset = queryset[:cutoff]

return OrderedDict([
(self.to_representation(item), self.display_value(item))
for item in queryset
])

@property
def choices(self):
return self.get_choices()

@property
def grouped_choices(self):
return self.choices

def display_value(self, instance):
return str(instance)


class StringRelatedField(relations.StringRelatedField, RelatedField):
pass


class PrimaryKeyRelatedField(relations.PrimaryKeyRelatedField,
RelatedField):

def to_internal_value(self, data):
if self.pk_field is not None:
data = self.pk_field.to_internal_value(data)
try:
return self.get_queryset().get(pk=data)
except ObjectDoesNotExist:
self.raise_error('does_not_exist', pk_value=data)
except (TypeError, ValueError):
self.raise_error('incorrect_type', data_type=type(data).__name__)

def to_representation(self, value):
if self.pk_field is not None:
return self.pk_field.to_representation(value.pk)
return value.pk


class HyperlinkedRelatedField(relations.HyperlinkedRelatedField,
RelatedField):
lookup_field = 'pk'

def use_pk_only_optimization(self):
return self.lookup_field == 'pk'

def get_object(self, view_name, view_args, view_kwargs):
"""
Return the object corresponding to a matched URL.
Takes the matched URL conf arguments, and should return an
object instance, or raise an `ObjectDoesNotExist` exception.
"""
# TODO: Check on getting this values correctly
lookup_value = view_kwargs[self.lookup_url_kwarg]
lookup_kwargs = {self.lookup_field: lookup_value}
return self.get_queryset().get(**lookup_kwargs)

def is_saved_in_database(self, obj):
if not obj or not obj.pk:
return False
return True

def get_lookup_value(self, obj):
# TODO: extract pk fields and return pk values as tuple
pass


class HyperlinkedIdentityField(relations.HyperlinkedIdentityField,
HyperlinkedRelatedField):
pass


class SlugRelatedField(RelatedField):
"""
A read-write field that represents the target of the relationship
by a unique 'slug' attribute.
"""
default_error_messages = {
'does_not_exist': u'Object with {slug_name}={value} does not exist.',
'invalid': u'Invalid value.',
}

def __init__(self, slug_field=None, **kwargs):
assert slug_field is not None, 'The `slug_field` argument is required.'
self.slug_field = slug_field
super(SlugRelatedField, self).__init__(**kwargs)

def to_internal_value(self, data):
try:
return self.get_queryset().get(**{self.slug_field: data})
except ObjectDoesNotExist:
self.raise_error(
'does_not_exist', slug_name=self.slug_field,
value=smart_text(data)
)
except (TypeError, ValueError):
self.raise_error('invalid')

def to_representation(self, obj):
return getattr(obj, self.slug_field)
4 changes: 2 additions & 2 deletions aiorest_ws/db/orm/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class Hyperlink(str):
We use this for hyperlinked URLs that may render as a named link
in some contexts, or render as a plain URL in others.
"""
is_hyperlink = True

def __new__(self, url, name):
ret = str.__new__(self, url)
ret.name = name
Expand All @@ -56,8 +58,6 @@ def __new__(self, url, name):
def __getnewargs__(self):
return str(self), self.name

is_hyperlink = True


class PKOnlyObject(object):
"""
Expand Down

0 comments on commit de50197

Please sign in to comment.