Make a generic django field for storing OpaqueKey classes
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
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
|
|||||||
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'):
|
|||||||
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):
|
|||||||
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):
|
|||||||
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):
|
class CourseKeyField(OpaqueKeyField):
|
||||||
|
description = "A CourseKey object, saved to the DB in the form of a string"
|
||||||
|
KEY_CLASS = CourseKey
|
||||||
|
|
||||||
|
|
||||||
|
class UsageKeyField(OpaqueKeyField):
|
||||||
description = "A Location object, saved to the DB in the form of a string"
|
description = "A Location object, saved to the DB in the form of a string"
|
||||||
|
KEY_CLASS = UsageKey
|
||||||
|
|
||||||
__metaclass__ = models.SubfieldBase
|
|
||||||
|
|
||||||
Empty = object()
|
class LocationKeyField(UsageKeyField):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
warnings.warn("LocationKeyField is deprecated. Please use UsageKeyField instead.", stacklevel=2)
|
||||||
|
super(LocationKeyField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
if value is self.Empty or value is None:
|
|
||||||
return value
|
|
||||||
|
|
||||||
assert isinstance(value, (basestring, UsageKey))
|
|
||||||
|
|
||||||
if value == '':
|
add_introspection_rules([], ["^xmodule_django\.models\.CourseKeyField"])
|
||||||
return None
|
add_introspection_rules([], ["^xmodule_django\.models\.LocationKeyField"])
|
||||||
|
add_introspection_rules([], ["^xmodule_django\.models\.UsageKeyField"])
|
||||||
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
|
|
||||||
return super(LocationKeyField, self).get_prep_lookup(
|
|
||||||
lookup,
|
|
||||||
# strip key before comparing
|
|
||||||
_strip_value(value, lookup)
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_prep_value(self, value):
|
|
||||||
if value is self.Empty:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
assert isinstance(value, UsageKey)
|
|
||||||
return unicode(_strip_value(value))
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user