diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py
index a7f95ea3c049aafcdedc86de3f3f843f68ecb14a..e24111dbb77e6cb7b6964cb86e0c660a8e03bf2a 100644
--- a/cms/djangoapps/contentstore/management/commands/import.py
+++ b/cms/djangoapps/contentstore/management/commands/import.py
@@ -25,8 +25,8 @@ class Command(BaseCommand):
 
         module_store = XMLModuleStore(org, course, data_dir, 'xmodule.raw_module.RawDescriptor')
         for module in module_store.modules.itervalues():
-            keystore().create_item(module.url)
+            keystore().create_item(module.location)
             if 'data' in module.definition:
-                keystore().update_item(module.url, module.definition['data'])
+                keystore().update_item(module.location, module.definition['data'])
             if 'children' in module.definition:
-                keystore().update_children(module.url, module.definition['children'])
+                keystore().update_children(module.location, module.definition['children'])
diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html
index 38b1cd9d94ad539b00ff56623275be94ea416030..bed0d1b4f804394591aaa5b487ca9fe40b5c0cb9 100644
--- a/cms/templates/widgets/navigation.html
+++ b/cms/templates/widgets/navigation.html
@@ -38,7 +38,7 @@
     % for week in weeks:
     <li>
       <header>
-        <h1><a href="#" class="module-edit" id="${week.url}">${week.name}</a></h1>
+        <h1><a href="#" class="module-edit" id="${week.location.url()}">${week.name}</a></h1>
         <ul>
           % if week.goals:
             % for goal in week.goals:
@@ -52,8 +52,8 @@
 
       <ul>
         % for module in week.get_children():
-        <li class="${module.type}">
-            <a href="#" class="module-edit" id="${module.url}">${module.name}</a>
+        <li class="${module.category}">
+            <a href="#" class="module-edit" id="${module.location.url()}">${module.name}</a>
             <a href="#" class="draggable">handle</a>
         </li>
         % endfor
diff --git a/cms/templates/widgets/sequence-edit.html b/cms/templates/widgets/sequence-edit.html
index abeec9209d97ec41666299632c95791c128bc3cb..319e137638557e21652a30c5ff948de3857389e6 100644
--- a/cms/templates/widgets/sequence-edit.html
+++ b/cms/templates/widgets/sequence-edit.html
@@ -37,7 +37,7 @@
           <ol>
             % for child in module.get_children():
             <li>
-              <a href="#" class="module-edit" id="${child.url}">${child.name}</a>
+              <a href="#" class="module-edit" id="${child.location.url()}">${child.name}</a>
               <a href="#" class="draggable">handle</a>
             </li>
             %endfor
diff --git a/common/lib/keystore/__init__.py b/common/lib/keystore/__init__.py
index b204e487e22b36e2d85f2a181c900699d56a538c..dab758fef1227705cae279cdd3cb7031a503f286 100644
--- a/common/lib/keystore/__init__.py
+++ b/common/lib/keystore/__init__.py
@@ -4,6 +4,7 @@ that are stored in a database an accessible using their Location as an identifie
 """
 
 import re
+from collections import namedtuple
 from .exceptions import InvalidLocationError
 
 URL_RE = re.compile("""
@@ -17,8 +18,8 @@ URL_RE = re.compile("""
 
 INVALID_CHARS = re.compile(r"[^\w.-]")
 
-
-class Location(object):
+_LocationBase = namedtuple('LocationBase', 'tag org course category name revision')
+class Location(_LocationBase):
     '''
     Encodes a location.
 
@@ -28,6 +29,7 @@ class Location(object):
     However, they can also be represented a dictionaries (specifying each component),
     tuples or list (specified in order), or as strings of the url
     '''
+    __slots__ = ()
 
     @classmethod
     def clean(cls, value):
@@ -36,7 +38,7 @@ class Location(object):
         """
         return re.sub('_+', '_', INVALID_CHARS.sub('_', value))
 
-    def __init__(self, location):
+    def __new__(_cls, loc_or_tag, org=None, course=None, category=None, name=None, revision=None):
         """
         Create a new location that is a clone of the specifed one.
 
@@ -60,47 +62,50 @@ class Location(object):
         Components may be set to None, which may be interpreted by some contexts to mean
         wildcard selection
         """
-        self.update(location)
 
-    def update(self, location):
-        """
-        Update this instance with data from another Location object.
+        if org is None and course is None and category is None and name is None and revision is None:
+            location = loc_or_tag
+        else:
+            location = (loc_or_tag, org, course, category, name, revision)
 
-        location: can take the same forms as specified by `__init__`
-        """
-        self.tag = self.org = self.course = self.category = self.name = self.revision = None
+        def check_dict(dict_):
+            check_list(dict_.values())
+
+        def check_list(list_):
+            for val in list_:
+                if val is not None and INVALID_CHARS.search(val) is not None:
+                    raise InvalidLocationError(location)
 
         if isinstance(location, basestring):
             match = URL_RE.match(location)
             if match is None:
                 raise InvalidLocationError(location)
             else:
-                self.update(match.groupdict())
-        elif isinstance(location, list):
+                groups = match.groupdict()
+                check_dict(groups)
+                return _LocationBase.__new__(_cls, **groups)
+        elif isinstance(location, (list, tuple)):
             if len(location) not in (5, 6):
                 raise InvalidLocationError(location)
 
-            (self.tag, self.org, self.course, self.category, self.name) = location[0:5]
-            self.revision = location[5] if len(location) == 6 else None
+            if len(location) == 5:
+                args = tuple(location) + (None, )
+            else:
+                args = tuple(location)
+
+            check_list(args)
+            return _LocationBase.__new__(_cls, *args)
         elif isinstance(location, dict):
-            try:
-                self.tag = location['tag']
-                self.org = location['org']
-                self.course = location['course']
-                self.category = location['category']
-                self.name = location['name']
-            except KeyError:
-                raise InvalidLocationError(location)
-            self.revision = location.get('revision')
+            kwargs = dict(location)
+            kwargs.setdefault('revision', None)
+
+            check_dict(kwargs)
+            return _LocationBase.__new__(_cls, **kwargs)
         elif isinstance(location, Location):
-            self.update(location.list())
+            return _LocationBase.__new__(_cls, location)
         else:
             raise InvalidLocationError(location)
 
-        for val in self.list():
-            if val is not None and INVALID_CHARS.search(val) is not None:
-                raise InvalidLocationError(location)
-
     def url(self):
         """
         Return a string containing the URL for this location
@@ -114,38 +119,19 @@ class Location(object):
         """
         Return a string with a version of the location that is safe for use in html id attributes
         """
-        return "-".join(str(v) for v in self.list() if v is not None)
-
-    def list(self):
-        """
-        Return a list representing this location
-        """
-        return [self.tag, self.org, self.course, self.category, self.name, self.revision]
+        return "-".join(str(v) for v in self if v is not None)
 
     def dict(self):
-        """
-        Return a dictionary representing this location
-        """
-        return {'tag': self.tag,
-                'org': self.org,
-                'course': self.course,
-                'category': self.category,
-                'name': self.name,
-                'revision': self.revision}
+        return self.__dict__
+
+    def list(self):
+        return list(self)
 
     def __str__(self):
         return self.url()
 
     def __repr__(self):
-        return 'Location(%r)' % str(self)
-
-    def __hash__(self):
-        return self.url()
-
-    def __eq__(self, other):
-        return (isinstance(other, Location) and
-                str(self) == str(other))
-
+        return "Location%r" % tuple(self)
 
 class ModuleStore(object):
     """
diff --git a/common/lib/keystore/tests/test_location.py b/common/lib/keystore/tests/test_location.py
index f10f03c0b01a5e2ec5776ebf62f31f6fb3ae4fe9..01d36d946b6f023d504f323c35a6500fd698df76 100644
--- a/common/lib/keystore/tests/test_location.py
+++ b/common/lib/keystore/tests/test_location.py
@@ -1,4 +1,4 @@
-from nose.tools import assert_equals, assert_raises
+from nose.tools import assert_equals, assert_raises, assert_not_equals
 from keystore import Location
 from keystore.exceptions import InvalidLocationError
 
@@ -11,7 +11,6 @@ def check_string_roundtrip(url):
 def test_string_roundtrip():
     check_string_roundtrip("tag://org/course/category/name")
     check_string_roundtrip("tag://org/course/category/name/revision")
-    check_string_roundtrip("tag://org/course/category/name with spaces/revision")
 
 
 def test_dict():
@@ -50,3 +49,15 @@ def test_invalid_locations():
     assert_raises(InvalidLocationError, Location, ["foo", "bar"])
     assert_raises(InvalidLocationError, Location, ["foo", "bar", "baz", "blat", "foo/bar"])
     assert_raises(InvalidLocationError, Location, None)
+    assert_raises(InvalidLocationError, Location, "tag://org/course/category/name with spaces/revision")
+
+def test_equality():
+    assert_equals(
+        Location('tag', 'org', 'course', 'category', 'name'),
+        Location('tag', 'org', 'course', 'category', 'name')
+    )
+
+    assert_not_equals(
+        Location('tag', 'org', 'course', 'category', 'name1'),
+        Location('tag', 'org', 'course', 'category', 'name')
+    )
diff --git a/common/lib/keystore/xml.py b/common/lib/keystore/xml.py
index c3327b8ff39eb7a59cf513e82b008e107e2f1786..e7adb56ad6e98a69639c26810dfd1fb6a566930b 100644
--- a/common/lib/keystore/xml.py
+++ b/common/lib/keystore/xml.py
@@ -47,7 +47,7 @@ class XMLModuleStore(ModuleStore):
                             xml_data.set('slug', '{tag}_{count}'.format(tag=xml_data.tag, count=self.unnamed_modules))
 
                         module = XModuleDescriptor.load_from_xml(etree.tostring(xml_data), self, org, course, modulestore.default_class)
-                        modulestore.modules[module.url] = module
+                        modulestore.modules[module.location] = module
                         return module
 
                     XMLParsingSystem.__init__(self, modulestore.get_item, OSFS(data_dir), process_xml)
@@ -68,7 +68,7 @@ class XMLModuleStore(ModuleStore):
         """
         location = Location(location)
         try:
-            return self.modules[location.url()]
+            return self.modules[location]
         except KeyError:
             raise ItemNotFoundError(location)
 
diff --git a/common/lib/xmodule/abtest_module.py b/common/lib/xmodule/abtest_module.py
index 3bd268184a356a827330a1b22285177768d8a507..beaeb4ad1cd56827c725042e1078655bfe11254a 100644
--- a/common/lib/xmodule/abtest_module.py
+++ b/common/lib/xmodule/abtest_module.py
@@ -102,7 +102,7 @@ class ABTestDescriptor(RawDescriptor, XmlDescriptor):
                 )
 
             child_content_urls = [
-                system.process_xml(etree.tostring(child)).url
+                system.process_xml(etree.tostring(child)).location.url()
                 for child in group
             ]
 
diff --git a/common/lib/xmodule/seq_module.py b/common/lib/xmodule/seq_module.py
index b11d540143b4fe2c79251a071fee5bb7126efb18..6d493b96ad7756307558b9e11144f7acfdea716a 100644
--- a/common/lib/xmodule/seq_module.py
+++ b/common/lib/xmodule/seq_module.py
@@ -105,6 +105,6 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor):
     @classmethod
     def definition_from_xml(cls, xml_object, system):
         return {'children': [
-            system.process_xml(etree.tostring(child_module)).url
+            system.process_xml(etree.tostring(child_module)).location.url()
             for child_module in xml_object
         ]}
diff --git a/common/lib/xmodule/x_module.py b/common/lib/xmodule/x_module.py
index 0b8fbb54d47947b56a6f1b348d3d8167f6cdec2e..191cda6b06d85530785872ac6d61287cc0eb6651 100644
--- a/common/lib/xmodule/x_module.py
+++ b/common/lib/xmodule/x_module.py
@@ -239,7 +239,7 @@ class XModuleDescriptor(Plugin):
         self.definition = definition if definition is not None else {}
         self.name = Location(kwargs.get('location')).name
         self.category = Location(kwargs.get('location')).category
-        self.url = Location(kwargs.get('location')).url()
+        self.location = Location(kwargs.get('location'))
         self.metadata = kwargs.get('metadata', {})
         self.shared_state_key = kwargs.get('shared_state_key')
 
@@ -364,7 +364,7 @@ class XModuleDescriptor(Plugin):
         return partial(
             self.module_class,
             system,
-            self.url,
+            self.location,
             self.definition,
             metadata=self.metadata
         )
diff --git a/lms/djangoapps/courseware/models.py b/lms/djangoapps/courseware/models.py
index 262d177248a5f94d3a529e8f04e96503428db2c1..f0bd8dc17e0df7a8f90be5bd78697013a50a050f 100644
--- a/lms/djangoapps/courseware/models.py
+++ b/lms/djangoapps/courseware/models.py
@@ -85,6 +85,7 @@ class StudentModuleCache(object):
                     student=user,
                     module_state_key__in=id_chunk)
                 )
+
         else:
             self.cache = []
 
@@ -93,7 +94,7 @@ class StudentModuleCache(object):
         Get a list of the state_keys needed for StudentModules
         required for this chunk of module xml
         '''
-        keys = [descriptor.url]
+        keys = [descriptor.location.url()]
 
         shared_state_key = getattr(descriptor, 'shared_state_key', None)
         if shared_state_key is not None:
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 2d47a552484542f944f5f3c513066fcacc37552f..5119cc291025efe658fccba39918428a4aad2bb7 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -207,7 +207,7 @@ def get_module(user, request, location, student_module_cache, position=None):
     '''
     descriptor = keystore().get_item(location)
 
-    instance_module = student_module_cache.lookup(descriptor.category, descriptor.url)
+    instance_module = student_module_cache.lookup(descriptor.category, descriptor.location.url())
     shared_state_key = getattr(descriptor, 'shared_state_key', None)
     if shared_state_key is not None:
         shared_module = student_module_cache.lookup(descriptor.category, shared_state_key)
@@ -218,7 +218,7 @@ def get_module(user, request, location, student_module_cache, position=None):
     shared_state = shared_module.state if shared_module is not None else None
 
     # Setup system context for module instance
-    ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.url + '/'
+    ajax_url = settings.MITX_ROOT_URL + '/modx/' + descriptor.location.url() + '/'
 
     def _get_module(location):
         (module, _, _, _) = get_module(user, request, location, student_module_cache, position)
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index e1e1c16632b68adc5c4a5f6b48adb1a21f6d9282..48e9bcc7957a852b4bcba199af79c488a2f5cb8a 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -209,7 +209,7 @@ def index(request, course=None, chapter=None, section=None,
         course_location = multicourse_settings.get_course_location(course)
         section = get_section(course_location, chapter, section)
         student_module_cache = StudentModuleCache(request.user, section)
-        module, _, _, _ = get_module(request.user, request, section.url, student_module_cache)
+        module, _, _, _ = get_module(request.user, request, section.location, student_module_cache)
         context['content'] = module.get_html()
 
     result = render_to_response('courseware.html', context)