Commit ccbff2da by Calen Pennington

Make a generic django field for storing OpaqueKey classes

parent 0bce5569
import warnings
from django.db import models from django.db import models
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location from opaque_keys.edx.locations import SlashSeparatedCourseKey, Location
...@@ -5,8 +7,6 @@ from opaque_keys.edx.keys import CourseKey, UsageKey ...@@ -5,8 +7,6 @@ from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys.edx.locator import Locator from opaque_keys.edx.locator import Locator
from south.modelsinspector import add_introspection_rules from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^xmodule_django\.models\.CourseKeyField"])
add_introspection_rules([], ["^xmodule_django\.models\.LocationKeyField"])
class NoneToEmptyManager(models.Manager): class NoneToEmptyManager(models.Manager):
...@@ -67,32 +67,50 @@ def _strip_value(value, lookup='exact'): ...@@ -67,32 +67,50 @@ def _strip_value(value, lookup='exact'):
return stripped_value return stripped_value
class CourseKeyField(models.CharField): class OpaqueKeyField(models.CharField):
description = "A CourseKey object, saved to the DB in the form of a string" """
A django field for storing OpaqueKeys.
The baseclass will return the value from the database as a string, rather than an instance
of an OpaqueKey, leaving the application to determine which key subtype to parse the string
as.
Subclasses must specify a KEY_CLASS attribute, in which case the field will use :meth:`from_string`
to parse the key string, and will return an instance of KEY_CLASS.
"""
description = "An OpaqueKey object, saved to the DB in the form of a string."
__metaclass__ = models.SubfieldBase __metaclass__ = models.SubfieldBase
Empty = object() Empty = object()
KEY_CLASS = None
def __init__(self, *args, **kwargs):
if self.KEY_CLASS is None:
raise ValueError('Must specify KEY_CLASS in OpaqueKeyField subclasses')
super(OpaqueKeyField, self).__init__(*args, **kwargs)
def to_python(self, value): def to_python(self, value):
if value is self.Empty or value is None: if value is self.Empty or value is None:
return None return None
assert isinstance(value, (basestring, CourseKey)) assert isinstance(value, (basestring, self.KEY_CLASS))
if value == '': if value == '':
# handle empty string for models being created w/o fields populated # handle empty string for models being created w/o fields populated
return None return None
if isinstance(value, basestring): if isinstance(value, basestring):
return CourseKey.from_string(value) return self.KEY_CLASS.from_string(value)
else: else:
return value return value
def get_prep_lookup(self, lookup, value): def get_prep_lookup(self, lookup, value):
if lookup == 'isnull': if lookup == 'isnull':
raise TypeError('Use CourseKeyField.Empty rather than None to query for a missing CourseKeyField') raise TypeError('Use {0}.Empty rather than None to query for a missing {0}'.format(self.__class__.__name__))
return super(CourseKeyField, self).get_prep_lookup( return super(OpaqueKeyField, self).get_prep_lookup(
lookup, lookup,
# strip key before comparing # strip key before comparing
_strip_value(value, lookup) _strip_value(value, lookup)
...@@ -102,7 +120,7 @@ class CourseKeyField(models.CharField): ...@@ -102,7 +120,7 @@ class CourseKeyField(models.CharField):
if value is self.Empty or value is None: if value is self.Empty or value is None:
return '' # CharFields should use '' as their empty value, rather than None return '' # CharFields should use '' as their empty value, rather than None
assert isinstance(value, CourseKey) assert isinstance(value, self.KEY_CLASS)
return unicode(_strip_value(value)) return unicode(_strip_value(value))
def validate(self, value, model_instance): def validate(self, value, model_instance):
...@@ -111,66 +129,33 @@ class CourseKeyField(models.CharField): ...@@ -111,66 +129,33 @@ class CourseKeyField(models.CharField):
if not self.blank and value is self.Empty: if not self.blank and value is self.Empty:
raise ValidationError(self.error_messages['blank']) raise ValidationError(self.error_messages['blank'])
else: else:
return super(CourseKeyField, self).validate(value, model_instance) return super(OpaqueKeyField, self).validate(value, model_instance)
def run_validators(self, value): def run_validators(self, value):
"""Validate Empty values, otherwise defer to the parent""" """Validate Empty values, otherwise defer to the parent"""
if value is self.Empty: if value is self.Empty:
return return
return super(CourseKeyField, self).run_validators(value) return super(OpaqueKeyField, self).run_validators(value)
class LocationKeyField(models.CharField):
description = "A Location object, saved to the DB in the form of a string"
__metaclass__ = models.SubfieldBase
Empty = object()
def to_python(self, value): class CourseKeyField(OpaqueKeyField):
if value is self.Empty or value is None: description = "A CourseKey object, saved to the DB in the form of a string"
return value KEY_CLASS = CourseKey
assert isinstance(value, (basestring, UsageKey))
if value == '':
return None
if isinstance(value, basestring):
return Location.from_deprecated_string(value)
else:
return value
def get_prep_lookup(self, lookup, value):
if lookup == 'isnull':
raise TypeError('Use LocationKeyField.Empty rather than None to query for a missing LocationKeyField')
# remove version and branch info before comparing keys class UsageKeyField(OpaqueKeyField):
return super(LocationKeyField, self).get_prep_lookup( description = "A Location object, saved to the DB in the form of a string"
lookup, KEY_CLASS = UsageKey
# strip key before comparing
_strip_value(value, lookup)
)
def get_prep_value(self, value):
if value is self.Empty:
return ''
assert isinstance(value, UsageKey) class LocationKeyField(UsageKeyField):
return unicode(_strip_value(value)) def __init__(self, *args, **kwargs):
warnings.warn("LocationKeyField is deprecated. Please use UsageKeyField instead.", stacklevel=2)
super(LocationKeyField, self).__init__(*args, **kwargs)
def validate(self, value, model_instance):
"""Validate Empty values, otherwise defer to the parent"""
# raise validation error if the use of this field says it can't be blank but it is
if not self.blank and value is self.Empty:
raise ValidationError(self.error_messages['blank'])
else:
return super(LocationKeyField, self).validate(value, model_instance)
def run_validators(self, value):
"""Validate Empty values, otherwise defer to the parent"""
if value is self.Empty:
return
return super(LocationKeyField, self).run_validators(value) add_introspection_rules([], ["^xmodule_django\.models\.CourseKeyField"])
add_introspection_rules([], ["^xmodule_django\.models\.LocationKeyField"])
add_introspection_rules([], ["^xmodule_django\.models\.UsageKeyField"])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment