# Copyright (C) 2007 Alexandre Conrad, alexandre (dot) conrad (at) gmail (dot) com
#
# This module is part of FormAlchemy and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

import logging
logger = logging.getLogger('formalchemy.' + __name__)

from copy import copy, deepcopy
import warnings

from sqlalchemy.orm import class_mapper
from sqlalchemy.orm.attributes import ScalarAttributeImpl, ScalarObjectAttributeImpl, CollectionAttributeImpl, InstrumentedAttribute
from sqlalchemy.orm.properties import CompositeProperty, ColumnProperty
from sqlalchemy.exceptions import InvalidRequestError # 0.4 support
from formalchemy import fatypes, validators, renderers
from formalchemy.utils import stringify, normalized_options, query_options
from formalchemy.utils import _pk, _pk_one_column, simple_eval
from formalchemy.renderers import *

__all__ = ['Field', 'AbstractField', 'AttributeField'] + renderers.__all__


def _foreign_keys(property):
    # 0.4/0.5 compatibility fn
    try:
        return property.foreign_keys
    except AttributeError:
        return [r for l, r in property.synchronize_pairs]


def _model_equal(a, b):
    if not isinstance(a, type):
        a = type(a)
    if not isinstance(b, type):
        b = type(b)
    return a is b


def _cache_deserialize(func):
    """Simple caching decorator"""
    def cache_decorator(self, *args, **kwargs):
        if self._deserialization_done:
            return self._deserialization_result
               
        self._deserialization_result = func(self, *args, **kwargs)
        self._deserialization_done = True

        return self._deserialization_result
    return cache_decorator


class AbstractField(object):
    """
    Contains the information necessary to render (and modify the rendering of)
    a form field

    Methods taking an `options` parameter will accept several ways of
    specifying those options:

    - an iterable of SQLAlchemy objects; `str()` of each object will be the description, and the primary key the value
    - a SQLAlchemy query; the query will be executed with `all()` and the objects returned evaluated as above
    - an iterable of (description, value) pairs
    - a dictionary of {description: value} pairs
    - a callable that return one of those cases. Used to evaluate options each time.

    Options can be "chained" indefinitely because each modification returns a new
    :mod:`Field <formalchemy.fields>` instance, so you can write::

      >>> from formalchemy.tests import FieldSet, User
      >>> fs = FieldSet(User)
      >>> fs.append(Field('foo').dropdown(options=[('one', 1), ('two', 2)]).radio())
      <FieldSet with ['id', 'email', 'password', 'name', 'orders', 'foo']>

    or::

      >>> fs.configure(options=[fs.name.label('Username').readonly()])

    Here is a callable exemple::

      >>> from formalchemy.tests import bill
      >>> def custom_query(fs):
      ...     return fs.session.query(User).filter(User.name=='Bill')
      >>> fs3 = FieldSet(bill)
      >>> fs3.configure(options=[fs3.name.dropdown(options=custom_query)])
      >>> print fs3.name.render()
      <select id="User-1-name" name="User-1-name"><option value="">None</option>
      <option value="1">Bill</option></select>


    """
    _null_option = (u'None', u'')

    def __init__(self, parent):
        # the FieldSet (or any ModelRenderer) owning this instance
        self.parent = parent
        if 0:
            import forms
            isinstance(self.parent, forms.FieldSet)
        # Renderer for this Field.  this will
        # be autoguessed, unless the user forces it with .dropdown,
        # .checkbox, etc.
        self._renderer = None
        # other render options, such as size, multiple, etc.
        self.render_opts = {}
        # validator functions added with .validate()
        self.validators = []
        # Prime cache for validation results
        self._deserialization_done = False
        self._deserialization_result = None
        # errors found by _validate() (which runs implicit and
        # explicit validators)
        self.errors = []
        self._readonly = False
        # label to use for the rendered field.  autoguessed if not specified by .label()
        self.label_text = None
        # optional attributes to pass to renderers
        self.html_options = {}
        # True iff this Field is a primary key
        self.is_pk = False
        # True iff this Field is a raw foreign key
        self.is_raw_foreign_key = False
        # Field metadata, for customization
        self.metadata = {}
        return False

    def _reset_cache(self):
        """Reset the cache value after deserialization. Used when
        rebinding a FieldSet with new data, to ensure new values
        are going to be deserialized."""
        self._deserialization_done = False
        self._deserialization_result = None

    def __deepcopy__(self, memo):
        wrapper = copy(self)
        wrapper.render_opts = dict(self.render_opts)
        wrapper.validators = list(self.validators)
        wrapper.errors = list(self.errors)
        try:
            wrapper._renderer = copy(self._renderer)
        except TypeError: # 2.4 support
            # it's a lambda, safe to just use same referende
            pass
        if hasattr(wrapper._renderer, 'field'):
            wrapper._renderer.field = wrapper
        return wrapper

    def requires_label(self):
        return not isinstance(self.renderer, renderers.HiddenFieldRenderer)
    requires_label = property(requires_label)

    def query(self, *args, **kwargs):
        """Perform a query in the parent's session"""
        if not self.parent.session:
            raise Exception("No session found.  Either bind a session explicitly, or specify relation options manually so FormAlchemy doesn't try to autoload them.")
        return self.parent.session.query(*args, **kwargs)

    def _validate(self):
        if self.is_readonly():
            return True

        self.errors = []

        try:
            # Call renderer.deserialize(), because the deserializer can
            # also raise a ValidationError
            value = self._deserialize()
        except validators.ValidationError, e:
            self.errors.append(e)
            return False

        L = list(self.validators)
        if self.is_required() and validators.required not in L:
            L.append(validators.required)
        for validator in L:
            if validator is not validators.required and value is None:
                continue
            try:
                validator(value, self)
            except validators.ValidationError, e:
                self.errors.append(e.message)
            except TypeError, e:
                warnings.warn(DeprecationWarning('Please provide a field argument to your %r validator. Your validator will break in FA 1.5' % validator))
                try:
                    validator(value)
                except validators.ValidationError, e:
                    self.errors.append(e.message)
        return not self.errors

    def is_required(self):
        """True iff this Field must be given a non-empty value"""
        return validators.required in self.validators

    def is_readonly(self):
        """True iff this Field is in readonly mode"""
        return self._readonly

    def model(self):
        return self.parent.model
    model = property(model)

    def _modified(self, **kwattrs):
        # return a copy of self, with the given attributes modified
        copied = deepcopy(self)
        for attr, value in kwattrs.iteritems():
            setattr(copied, attr, value)
        return copied

    def set(self, **kwattrs):
        """
        Update field settings in place. Allowed attributes are: validate,
        renderer, required, readonly, nul_as, label, multiple, options, size.
        You can also specify custom settings, like 'help', 'separator',
        'stack' or anything you could use in your templates to make your
        life easier.

            >>> field = Field('myfield')
            >>> field.set(label='My field', renderer=SelectFieldRenderer,
            ...           options=[('Value', 1)])
            AttributeField(myfield)
            >>> field.label_text
            'My field'
            >>> field.renderer
            <SelectFieldRenderer for AttributeField(myfield)>

        """
        attrs = kwattrs.keys()
        mapping = dict(renderer='_renderer',
                       readonly='_readonly',
                       null_as='_null_option',
                       label='label_text')
        for attr in attrs:
            value = kwattrs.pop(attr)
            if attr == 'validate':
                if isinstance(value, (list, tuple)):
                    self.validators.extend(value)
                elif callable(value):
                    self.validators.append(value)
                else:
                    raise ValueError("set(validate=...) must be called with either a callable or a list/tuple")
            elif attr == 'required':
                if value:
                    if validators.required not in self.validators:
                        self.validators.append(validators.required)
                else:
                    if validators.required in self.validators:
                        self.validators.remove(validators.required)
            elif attr in mapping:
                attr = mapping.get(attr)
                setattr(self, attr, value)
            else:
                # All others too
                if attr == 'options' and value is not None:
                    value = normalized_options(value)
                self.render_opts[attr] = value
        return self

    def get(self, attr, default=None):
        """Return the setting for this attr. Complements `set()`.

        This function works a little bit like the `dict` method `get()`,
        as it accepts a second parameter which is the default value.
        """
        mapping = dict(renderer='_renderer',
                       readonly='_readonly',
                       null_as='_null_option',
                       label='label_text')
        if attr in mapping:
            return getattr(self, mapping[attr], default)
        else:
            return self.render_opts.get(attr, default)

    def with_null_as(self, option):
        """Render null as the given option tuple of text, value."""
        return self._modified(_null_option=option)
    def with_renderer(self, renderer):
        """
        Return a copy of this Field, with a different renderer.
        Used for one-off renderer changes; if you want to change the
        renderer for all instances of a Field type, modify
        FieldSet.default_renderers instead.
        """
        return self._modified(_renderer=renderer)
    def bind(self, parent):
        """Return a copy of this Field, bound to a different parent"""
        return self._modified(parent=parent)
    def with_metadata(self, **attrs):
        """Attach some metadata attributes to the Field, to be used by
        conditions in templates.

        Example usage:

          >>> test = Field('test')
          >>> field = test.with_metadata(instructions='use this widget this way')
          ...

        And further in your templates you can verify:

          >>> 'instructions' in field.metadata
          True

        and display the content in a <span> or something.
        """
        new_attr = self.metadata.copy()
        new_attr.update(attrs)
        return self._modified(metadata=new_attr)
    def validate(self, validator):
        """
        Add the `validator` function to the list of validation
        routines to run when the `FieldSet`'s `validate` method is
        run. Validator functions take one parameter: the value to
        validate. This value will have already been turned into the
        appropriate data type for the given `Field` (string, int, float,
        etc.). It should raise `ValidationError` if validation
        fails with a message explaining the cause of failure.
        """
        field = deepcopy(self)
        field.validators.append(validator)
        return field
    def required(self):
        """
        Convenience method for `validate(validators.required)`. By
        default, NOT NULL columns are required. You can only add
        required-ness, not remove it.
        """
        return self.validate(validators.required)
    def with_html(self, **html_options):
        """
        Give some HTML options to renderer.

        Trailing underscore (_) characters will be stripped. For example,
        you might want to add a `class` attribute to your checkbox. You
        would need to specify `.options(class_='someclass')`.

        For WebHelpers-aware people: those parameters will be passed to
        the `text_area()`, `password()`, `text()`, etc.. webhelpers.

        NOTE: Those options can override generated attributes and can mess
              the `sync` calls, or `label`-tag associations (if you change
              `name`, or `id` for example).  Use with caution.
        """
        new_opts = copy(self.html_options)
        for k, v in html_options.iteritems():
            new_opts[k.rstrip('_')] = v
        return self._modified(html_options=new_opts)
    def label(self, text):
        """
        Change the label associated with this field.  By default, the field name
        is used, modified for readability (e.g., 'user_name' -> 'User name').
        """
        return self._modified(label_text=text)
    def readonly(self, value=True):
        """Render the field readonly."""
        return self._modified(_readonly=True)
    def hidden(self):
        """Render the field hidden.  (Value only, no label.)"""
        return self._modified(_renderer=renderers.HiddenFieldRenderer,
                              render_opts={})
    def password(self):
        """Render the field as a password input, hiding its value."""
        field = deepcopy(self)
        field._renderer = lambda: field.parent.default_renderers['password']
        field.render_opts = {}
        return field
    def textarea(self, size=None):
        """
        Render the field as a textarea.  Size must be a string
        (`"25x10"`) or tuple (`25, 10`).
        """
        field = deepcopy(self)
        field._renderer = lambda: field.parent.default_renderers['textarea']
        if size:
            field.render_opts = {'size': size}
        return field
    def radio(self, options=None):
        """Render the field as a set of radio buttons."""
        field = deepcopy(self)
        field._renderer = lambda: field.parent.default_renderers['radio']
        if options is None:
            options = self.render_opts.get('options')
        else:
            options = normalized_options(options)
        field.render_opts = {'options': options}
        return field
    def checkbox(self, options=None):
        """Render the field as a set of checkboxes."""
        field = deepcopy(self)
        field._renderer = lambda: field.parent.default_renderers['checkbox']
        if options is None:
            options = self.render_opts.get('options')
        else:
            options = normalized_options(options)
        field.render_opts = {'options': options}
        return field
    def dropdown(self, options=None, multiple=False, size=5):
        """
        Render the field as an HTML select field.
        (With the `multiple` option this is not really a 'dropdown'.)
        """
        field = deepcopy(self)
        field._renderer = lambda: field.parent.default_renderers['dropdown']
        if options is None:
            options = self.render_opts.get('options')
        else:
            options = normalized_options(options)
        field.render_opts = {'multiple': multiple, 'options': options}
        if multiple:
            field.render_opts['size'] = size
        return field
    def reset(self):
        """
        Return the field with all configuration changes reverted.
        """
        return deepcopy(self.parent._fields[self.name])

    def _get_renderer(self):
        for t in self.parent.default_renderers:
            if not isinstance(t, basestring) and isinstance(self.type, t):
                return self.parent.default_renderers[t]
        raise TypeError(
                'No renderer found for field %s. '
                'Type %s as no default renderer' % (self.name, self.type))

    def renderer(self):
        if self._renderer is None:
            self._renderer = self._get_renderer()
        if callable(self._renderer):
            # invoke potential lambda
            try:
                self._renderer = self._renderer()
            except TypeError:
                pass
        if callable(self._renderer):
            # must be a Renderer class.  instantiate.
            self._renderer = self._renderer(self)
        return self._renderer
    renderer = property(renderer)

    def _get_render_opts(self):
        """
        Calculate the final options dict to be sent to renderers.
        """
        # Use options from internally set render_opts
        opts = dict(self.render_opts)
        # Override with user-specified options (with .with_html())
        opts.update(self.html_options)
        return opts

    def render(self):
        """
        Render this Field as HTML.
        """
        if self.is_readonly():
            return self.render_readonly()

        opts = self._get_render_opts()

        if (isinstance(self.type, fatypes.Boolean)
            and not opts.get('options')
            and self.renderer.__class__ in [self.parent.default_renderers['dropdown'], self.parent.default_renderers['radio']]):
            opts['options'] = [('Yes', True), ('No', False)]
        return self.renderer.render(**opts)

    def render_readonly(self):
        """
        Render this Field as HTML for read only mode.
        """
        return self.renderer.render_readonly(**self._get_render_opts())

    def _pkify(self, value):
        """return the PK for value, if applicable"""
        return value

    def value(self):
        """
        The value of this Field: use the corresponding value in the bound `data`,
        if any; otherwise, use the value in the bound `model`.  For SQLAlchemy models,
        if there is still no value, use the default defined on the corresponding `Column`.

        For SQLAlchemy collections,
        a list of the primary key values of the items in the collection is returned.

        Invalid form data will cause an error to be raised.  Controllers should thus validate first.
        Renderers should thus never access .value; use .model_value instead.  
        """
        # TODO add ._validated flag to save users from themselves?
        if not self.is_readonly() and self.parent.data is not None:
            v = self._deserialize()
            if v is not None:
                return self._pkify(v)
        return self.model_value
    value = property(value)

    def value_objects(self):
        """This is the same as `value`, except that when used with ForeignKeys,
        instead of returning a list of primary keys, it will return a list of
        objects.
        """
        if not self.is_readonly() and self.parent.data is not None:
            return self._deserialize()
        return self.model_value
    value_objects = property(value_objects)

    def model_value(self):
        """
        raw value from model, transformed if necessary for use as a form input value.
        """
        raise NotImplementedError()
    model_value = property(model_value)
        
    def raw_value(self):
        """
        raw value from model.  different from `.model_value` in SQLAlchemy fields, because for reference types,
        `.model_value` will return the foreign key ID.  This will return the actual object 
        referenced instead.
        """
        raise NotImplementedError()

    @_cache_deserialize
    def _deserialize(self):
        return self.renderer.deserialize()

class Field(AbstractField):
    """
    A manually-added form field.

    This is the object that will be used if you create a field and add
    it to a FieldSet using the `append()` method. Default values will be
    the one passed as the `value` parameter. Raw model value will be that
    default value also.

    NOTE: you can override `raw_value` to return some data actually
    fetched from the model, through `self.model.some_attribute`, if you
    added a field to a FieldSet which is bound to an SQLAlchemy model.
    This way you could completely customize the behavior of your widget.
    Keep in mind that you will have to save the renderers' `.value` to
    your model's attribute manually. You should simply need to assign your
    `[FieldSet].[field_name].value` to your model, somewhere after calling
    `sync()` on your `FieldSet`.
    """
    def __init__(self, name=None, type=fatypes.String, value=None, **kwattrs):
        """
        Create a new Field object.

        - `name`: 
              field name

        - `type=types.String`: 
              data type, from formalchemy.types (Integer, Float, String, Binary,
              Boolean, Date, DateTime, Time) or a custom type

        - `value=None`: 
              default value.  If value is a callable, it will be passed the current
              bound model instance when the value is read.  This allows creating a
              Field whose value depends on the model once, then binding different
              instances to it later.

          * `name`: field name
          * `type`: data type, from formalchemy.types (Boolean, Integer, String, etc.),
            or a custom type for which you have added a renderer.
          * `value`: default value.  If value is a callable, it will be passed
            the current bound model instance when the value is read.  This allows
            creating a Field whose value depends on the model once, then
            binding different instances to it later.
        """
        AbstractField.__init__(self, None) # parent will be set by ModelRenderer.add
        self.type = type()
        self.name = self.key = name
        self._value = value
        self.is_relation = False
        self.is_scalar_relation = False
        self.set(**kwattrs)

    def set(self, **kwattrs):
        if 'value' in kwattrs:
            self._value = kwattrs.pop('value')
        return AbstractField.set(self, **kwattrs)

    def model_value(self):
        return self.raw_value
    model_value = property(model_value)

    def is_collection(self):
        return self.render_opts.get('multiple', False) or isinstance(self.renderer, self.parent.default_renderers['checkbox'])
    is_collection = property(is_collection)

    def raw_value(self):
        try:
            # this is NOT the same as getattr -- getattr will return the class's
            # value for the attribute name, which for a manually added Field will
            # be the Field object.  So force looking in the instance __dict__ only.
            return self.model.__dict__[self.name]
        except (KeyError, AttributeError):
            pass
        if callable(self._value):
            return self._value(self.model)
        return self._value
    raw_value = property(raw_value)

    def sync(self):
        """Set the attribute's value in `model` to the value given in `data`"""
        if not self.is_readonly():
            self._value = self._deserialize()

    def __repr__(self):
        return 'AttributeField(%s)' % self.name

    def __eq__(self, other):
        # we override eq so that when we configure with options=[...], we can match the renders in options
        # with the ones that were generated at FieldSet creation time
        try:
            return self.name == other.name and _model_equal(self.model, other.model)
        except (AttributeError, ValueError):
            return False
    def __hash__(self):
        return hash(self.name)


class AttributeField(AbstractField):
    """
    Field corresponding to an SQLAlchemy attribute.

    This class will be used automatically when mapping to an SQLAlchemy
    object. Default data will be taken from the table definitions. Raw
    model values will be taken from the model objects.
    """
    def __init__(self, instrumented_attribute, parent):
        """
            >>> from formalchemy.tests import FieldSet, Order
            >>> fs = FieldSet(Order)
            >>> print fs.user.key
            user

            >>> print fs.user.name
            user_id
        """
        AbstractField.__init__(self, parent)
        # we rip out just the parts we care about from InstrumentedAttribute.
        # impl is the AttributeImpl.  So far all we care about there is ".key,"
        # which is the name of the attribute in the mapped class.
        self._impl = instrumented_attribute.impl
        # property is the PropertyLoader which handles all the interesting stuff.
        # mapper, columns, and foreign keys are all located there.
        self._property = instrumented_attribute.property

        # True iff this is a multi-valued (one-to-many or many-to-many) SA relation
        self.is_collection = isinstance(self._impl, CollectionAttributeImpl)

        # True iff this is the 'one' end of a one-to-many relation
        self.is_scalar_relation = isinstance(self._impl, ScalarObjectAttributeImpl)

        # True iff this field represents a mapped SA relation
        self.is_relation = self.is_scalar_relation or self.is_collection

        self.is_composite = isinstance(self._property, CompositeProperty)

        _columns = self._columns

        self.is_pk = bool([c for c in self._columns if c.primary_key])

        self.is_raw_foreign_key = bool(isinstance(self._property, ColumnProperty) and _foreign_keys(self._property.columns[0]))

        self.is_composite_foreign_key = len(_columns) > 1 and not [c for c in _columns if not _foreign_keys(c)]

        if self.is_composite:
            # this is a little confusing -- we need to return an _instance_ of
            # the correct type, which for composite values will be the value
            # itself. SA should probably have called .type something
            # different, or just not instantiated them...
            self.type = self._property.composite_class.__new__(self._property.composite_class)
        elif len(_columns) > 1:
            self.type = None # may have to be more accurate here
        else:
            self.type = _columns[0].type

        self.key = self._impl.key
        self._column_name = '_'.join([c.name for c in _columns])

        # The name of the form input. usually the same as the key, except for
        # single-valued SA relation properties. For example, for order.user,
        # name will be 'user_id' (assuming that is indeed the name of the foreign
        # key to users), but for user.orders, name will be 'orders'.
        if self.is_collection or self.is_composite or not hasattr(self.model, self._column_name):
            self.name = self.key
        else:
            self.name = self._column_name

        # smarter default "required" value
        if not self.is_collection and not self.is_readonly() and [c for c in _columns if not c.nullable]:
            self.validators.append(validators.required)

    def is_readonly(self):
        from sqlalchemy.sql.expression import _Label
        return AbstractField.is_readonly(self) or isinstance(self._columns[0], _Label)

    def _columns(self):
        if self.is_scalar_relation:
            # If the attribute is a foreign key, return the Column that this
            # attribute is mapped from -- e.g., .user -> .user_id.
            return _foreign_keys(self._property)
        elif isinstance(self._impl, ScalarAttributeImpl) or self._impl.__class__.__name__ in ('ProxyImpl', '_ProxyImpl'): # 0.4 compatibility: ProxyImpl is a one-off class for each synonym, can't import it
            # normal property, mapped to a single column from the main table
            return self._property.columns
        else:
            # collection -- use the mapped class's PK
            assert self.is_collection, self._impl.__class__
            return self._property.mapper.primary_key
    _columns = property(_columns)

    def relation_type(self):
        """
        The type of object in the collection (e.g., `User`).
        Calling this is only valid when `is_relation` is True.
        """
        return self._property.mapper.class_

    def _pkify(self, value):
        """return the PK for value, if applicable"""
        if value is None:
            return None
        if self.is_collection:
            return [_pk(item) for item in value]
        return value

    def model_value(self):
        return self._pkify(self.raw_value)
    model_value = property(model_value)

    def raw_value(self):
        try:
            v = getattr(self.model, self.name)
        except AttributeError:
            v = getattr(self.model, self.key)
        if v is not None:
            return v

        _columns = self._columns
        if len(_columns) == 1 and  _columns[0].default:
            try:
                from sqlalchemy.sql.expression import Function
            except ImportError:
                from sqlalchemy.sql.expression import _Function as Function
            arg = _columns[0].default.arg
            if callable(arg) or isinstance(arg, Function):
                # callables often depend on the current time, e.g. datetime.now or the equivalent SQL function.
                # these are meant to be the value *at insertion time*, so it's not strictly correct to
                # generate a value at form-edit time.
                pass
            else:
                return arg
        return None
    raw_value = property(raw_value)

    def sync(self):
        """Set the attribute's value in `model` to the value given in `data`"""
        if not self.is_readonly():
            setattr(self.model, self.name, self._deserialize())

    def __eq__(self, other):
        # we override eq so that when we configure with options=[...], we can match the renders in options
        # with the ones that were generated at FieldSet creation time
        try:
            return self._impl is other._impl and _model_equal(self.model, other.model)
        except (AttributeError, ValueError):
            return False
    def __hash__(self):
        return hash(self._impl)

    def __repr__(self):
        return 'AttributeField(%s)' % self.key

    def render(self):
        if self.is_readonly():
            return self.render_readonly()
        if self.is_relation and self.render_opts.get('options') is None:
            if self.is_required() or self.is_collection:
                self.render_opts['options'] = []
            else:
                self.render_opts['options'] = [self._null_option]
            # todo 2.0 this does not handle primaryjoin (/secondaryjoin) alternate join conditions
            fk_cls = self.relation_type()
            order_by = self._property.order_by or list(class_mapper(fk_cls).primary_key)
            q = self.query(fk_cls).order_by(order_by)
            self.render_opts['options'] += query_options(q)
            logger.debug('options for %s are %s' % (self.name, self.render_opts['options']))
        if self.is_collection and isinstance(self.renderer, self.parent.default_renderers['dropdown']):
            self.render_opts['multiple'] = True
            if 'size' not in self.render_opts:
                self.render_opts['size'] = 5
        return AbstractField.render(self)

    def _get_renderer(self):
        if self.is_relation:
            return self.parent.default_renderers['dropdown']
        return AbstractField._get_renderer(self)

    @_cache_deserialize
    def _deserialize(self):
        # for multicolumn keys, we turn the string into python via
        # FA's utils.simple_eval; otherwise, the key is just the raw
        # deserialized value (which is already an int, etc., as necessary)
        if len(self._columns) > 1:
            python_pk = simple_eval
        else:
            python_pk = lambda st: st

        if self.is_collection:
            return [self.query(self.relation_type()).get(python_pk(pk)) for pk in self.renderer.deserialize()]
        if self.is_composite_foreign_key:
            return self.query(self.relation_type()).get(python_pk(self.renderer.deserialize()))
        return self.renderer.deserialize()
