diff --git a/src/mapfile.py b/src/mapfile.py index 0a0702ae88ee72416027ca3c2c953a1730b9d68c..bc7a1712ef4bbca3ea60dee1cc0bbf07d56a7023 100644 --- a/src/mapfile.py +++ b/src/mapfile.py @@ -37,6 +37,8 @@ import functools import maptools from webapp import KeyExists +import tools + class MetadataMixin(object): def __getattr__(self, attr): @@ -55,7 +57,7 @@ def get_store_connection_string(info): url = urlparse.urlparse(cparam["url"]) if url.scheme != "file" or url.netloc: raise ValueError("Only local files are suported.") - return url.path + return tools.get_resource_path(url.path) else: raise ValueError("Unhandled type '%s'" % cparam.get("dbtype", "<unknown>")) @@ -316,9 +318,9 @@ class FeatureType(LayerModel): self.set_metadata("wfs_enable_request", "!GetCapabilities !DescribeFeatureType !GetFeature") # Update mra metadatas, and make sure the mandatory ones are left untouched. + self.update_mra_metadatas(metadata) self.update_mra_metadatas({"name": ft_name, "type": "featuretype", "storage": ds_name, "workspace": ws.name, "is_model": True}) - self.update_metadatas(metadata) def configure_layer(self, layer, ws, enabled=True): @@ -381,7 +383,6 @@ class Coverage(LayerModel): def update(self, ws, c_name, cs_name, metadata): cs = ws.get_coveragestore(cs_name) - self.name = c_name # Set basic attributes. @@ -410,9 +411,9 @@ class Coverage(LayerModel): self.set_metadata("wcs_enable_request", "!GetCapabilities !DescribeCoverage !GetCoverage") # Update mra metadatas, and make sure the mandatory ones are left untouched. - self.update_mra_metadatas({"name": cs_name, "type": "coverage", "storage": cs_name, + self.update_mra_metadatas(metadata) + self.update_mra_metadatas({"name": c_name, "type": "coverage", "storage": cs_name, "workspace": ws.name, "is_model": True}) - self.update_metadatas(metadata) def configure_layer(self, layer, ws, enabled=True): diff --git a/src/metadata.py b/src/metadata.py index 5baaa62a437b71c8f4904a25b50f947f18706cff..398073806cfa5a1af6e01637b4c4b1347d8b7370 100644 --- a/src/metadata.py +++ b/src/metadata.py @@ -77,6 +77,7 @@ def set_metadatas(obj, metadatas): set_metadata(obj, key, value) def update_metadatas(obj, metadatas): + print metadatas for key, value in metadatas.iteritems(): set_metadata(obj, key, value) diff --git a/src/server.py b/src/server.py index 2bb7fd4b0279eb60fb13aec2f06cb793ad87f9c5..028c3bf153761493f1a6d99704519ac1970a5733 100755 --- a/src/server.py +++ b/src/server.py @@ -56,7 +56,7 @@ class tests(object): tpath = tools.safe_path_join(get_config('storage')['mapfiles'], "%s.map" % name) open(tpath, "w").write(open(spath).read()) - webapp.Created("%s/maps/%s.%s" % (web.ctx.home, name, format)) + webapp.Created("%s/maps/%s%s" % (web.ctx.home, name, format)) class mapfiles(object): @HTTPCompatible() @@ -86,12 +86,12 @@ class mapfiles(object): return {"mapfiles": mapfiles} - def POST(self, map_name): + def POST(self, map_name, format): data = get_data() # TODO: Create mapfile raise NotImplemented() - webapp.Created("%s/maps/%s" % (web.ctx.home, map_name)) + webapp.Created("%s/maps/%s%s" % (web.ctx.home, map_name, format)) class named_mapfile(object): @@ -157,8 +157,8 @@ class datastores(object): ws.create_datastore(ds_name, data) ws.save() - webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s" % ( - web.ctx.home, map_name, ws_name, ds_name)) + webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s%s" % ( + web.ctx.home, map_name, ws_name, ds_name, format)) class datastore(object): @@ -209,10 +209,11 @@ class featuretypes(object): data = get_data(name="featureType", mandatory=["name"]) with webapp.mightConflict("featureType", datastore=ds_name): - ws.create_featuretype(data["name"], ds_name, data) + with webapp.mightNotFound("featureType", datastore=ds_name): + ws.create_featuretype(data["name"], ds_name, data) ws.save() - webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s/featuretypes/%s.%s" % ( + webapp.Created("%s/maps/%s/workspaces/%s/datastores/%s/featuretypes/%s%s" % ( web.ctx.home, map_name, ws.name, ds_name, data["name"], format)) @@ -237,9 +238,9 @@ class featuretype(object): "name": map_name, "href": "%s/maps/%s/namespaces/%s.%s" % (web.ctx.home, map_name, ws_name, format) }, - "title": ft.get_metadata("title", ft.name), - "abstract": ft.get_metadata("abstract", None), - "keywords": ft.get_metadata("keywords", []), + "title": ft.get_mra_metadata("title", ft.name), + "abstract": ft.get_mra_metadata("abstract", None), + "keywords": ft.get_mra_metadata("keywords", []), "srs": dsft.get_projection(), "nativeCRS": dsft.get_native(), "attributes": [{ @@ -282,8 +283,10 @@ class featuretype(object): if ft_name != data["name"]: raise webapp.Forbidden("Can't change the name of a feature type.") + metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"]) + with webapp.mightNotFound("featureType", datastore=ds_name): - ws.update_featuretype(ft_name, ds_name, data) + ws.update_featuretype(ft_name, ds_name, metadata) ws.save() def DELETE(self, map_name, ws_name, ds_name, ft_name, format): @@ -309,15 +312,15 @@ class coveragestores(object): def POST(self, map_name, ws_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) - data = get_data(name="coverageStore", mandatory=["name", "connectionParameters"]) + data = get_data(name="coverageStore", mandatory=["name"]) cs_name = data.pop("name") with webapp.mightConflict("coverageStore", workspace=ws_name): ws.create_coveragestore(cs_name, data) ws.save() - webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s" % ( - web.ctx.home, map_name, ws_name, cs_name)) + webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s%s" % ( + web.ctx.home, map_name, ws_name, cs_name, format)) class coveragestore(object): @@ -335,7 +338,7 @@ class coveragestore(object): def PUT(self, map_name, ws_name, cs_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) - data = get_data(name="coverageStore", mandatory=["name", "type", "connectionParameters"], forbidden=["href"]) + data = get_data(name="coverageStore", mandatory=["name"], forbidden=["href"]) if cs_name != data.pop("name"): raise webapp.Forbidden("Can't change the name of a coverage store.") @@ -371,7 +374,7 @@ class coverages(object): ws.create_coverage(data["name"], cs_name, data) ws.save() - webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s/coverages/%s.%s" % ( + webapp.Created("%s/maps/%s/workspaces/%s/coveragestores/%s/coverages/%s%s" % ( web.ctx.home, map_name, ws.name, cs_name, data["name"], format)) @@ -379,7 +382,8 @@ class coverage(object): @HTTPCompatible() def GET(self, map_name, ws_name, cs_name, c_name, format): mf, ws = get_mapfile_workspace(map_name, ws_name) - c = ws.get_coverage(c_name, cs_name) + with webapp.mightNotFound("coverage", coveragestore=cs_name): + c = ws.get_coverage(c_name, cs_name) with webapp.mightNotFound("coveragestore", workspace=ws_name): cs = ws.get_coveragestore(cs_name) @@ -394,9 +398,9 @@ class coverage(object): "name": map_name, "href": "%s/maps/%s/namespaces/%s.%s" % (web.ctx.home, map_name, ws_name, format) }, - "title": c.get_metadata("title", c.name), - "abstract": c.get_metadata("abstract", None), - "keywords": c.get_metadata("keywords", []), + "title": c.get_mra_metadata("title", c.name), + "abstract": c.get_mra_metadata("abstract", None), + "keywords": c.get_mra_metadata("keywords", []), "srs": cs.get_projection(), "nativeBoundingBox": { "minx": extent.minX(), @@ -428,8 +432,11 @@ class coverage(object): if c_name != data["name"]: raise webapp.Forbidden("Can't change the name of a coverage.") + + metadata = dict((k, v) for k, v in data.iteritems() if k in ["title", "abstract"]) + with webapp.mightNotFound("coverage", coveragestore=cs_name): - ws.update_coverage(c_name, cs_name, data) + ws.update_coverage(c_name, cs_name, metadata) ws.save() def DELETE(self, map_name, ws_name, cs_name, c_name, format): @@ -631,7 +638,7 @@ class layers(object): model.create_layer(ws, mf, l_name, l_enabled) mf.save() - webapp.Created("%s/maps/%s/layers/%s" % (web.ctx.home, map_name, l_name)) + webapp.Created("%s/maps/%s/layers/%s%s" % (web.ctx.home, map_name, l_name, format)) class layer(object): @@ -747,7 +754,7 @@ class layerstyles(object): layer.add_style_sld(mf, s_name, style) mf.save() - webapp.Created("%s/maps/%s/layers/%s/layerstyles/%s" % (web.ctx.home, map_name, l_name, s_name)) + webapp.Created("%s/maps/%s/layers/%s/layerstyles/%s%s" % (web.ctx.home, map_name, l_name, s_name, format)) class layerstyle(object): @@ -798,7 +805,7 @@ class layergroups(object): mf.save() - webapp.Created("%s/maps/%s/layergroups/%s" % (web.ctx.home, map_name, lg.name)) + webapp.Created("%s/maps/%s/layergroups/%s%s" % (web.ctx.home, map_name, lg.name, format)) class layergroup(object): @@ -907,14 +914,13 @@ urlmap(layergroup, "maps", (), "layergroups", ()) urls = tuple(urlmap) -if get_config("debug")["web_debug"]: - web.config.debug = True -if get_config("logging")["web_logs"]: - HTTPCompatible.return_logs = True +web.config.debug = get_config("debug")["web_debug"] +webapp.exceptionManager.raise_all = get_config("debug")["raise_all"] +HTTPCompatible.return_logs = get_config("logging")["web_logs"] app = web.application(urls, globals()) if __name__ == "__main__": app.run() - +u application = app.wsgifunc() diff --git a/src/stores.py b/src/stores.py index 1f359e372dbd0bae563a56f8bca80b658d916a80..f6d921039ac25804a17653bf588f56e422bf57da 100644 --- a/src/stores.py +++ b/src/stores.py @@ -324,7 +324,7 @@ class Datastore(object): if item == None: raise IndexError("No layer '%s'" % key) else: item = self.backend.GetLayerByName(key.encode('ascii', 'ignore')) - if item == None: raise KeyError("No layer '%s'" % key) + if item == None: raise KeyError(key) return Featuretype(item, self) def nblayers(self): diff --git a/src/tools.py b/src/tools.py index b336810ac27b15189c9ab8821d6b907733db642f..9751995d44b467299b30290342e327bec5049621 100644 --- a/src/tools.py +++ b/src/tools.py @@ -76,8 +76,11 @@ def safe_path_join(root, *args): raise webapp.Forbidden(message="path '%s' outside root directory." % (args)) return full_path +def get_resource_path(*args): + return safe_path_join(get_config('storage')['resources'], *args) + def get_st_data_path(ws_name, st_type, name, *args): - return safe_path_join(get_config('storage')['resources'], "workspaces", ws_name, st_type, name, *args) + return get_resource_path("workspaces", ws_name, st_type, name, *args) def get_ds_data_path(ws_name, name, *args): return get_st_data_path(ws_name, "datastores", name, *args) @@ -137,7 +140,7 @@ def no_root(root, path): return path[len(root):] if path.startswith(root) else path def no_res_root(path): - return no_root(get_config('storage')['resources'], path) + return os.path.relpath(path, get_config('storage')['resources']) def is_hidden(path): # TODO Add a lot of checks, recursive option (to check folders) diff --git a/src/webapp.py b/src/webapp.py index 778e81763169cfc1db6933cd27bdb97a2663b33d..1e4c41b8ac2c164466c9adab6b325e75bebe8c9b 100644 --- a/src/webapp.py +++ b/src/webapp.py @@ -113,6 +113,8 @@ class NotImplemented(web.webapi.HTTPError): # The folowing helpers are for managing exceptions and transforming them into http errors: class exceptionManager(object): + raise_all = False + def __init__(self, exceptions): self.exceptions = exceptions @@ -120,7 +122,7 @@ class exceptionManager(object): pass def __exit__(self, exc_type, exc_value, traceback): - if exc_type in self.exceptions: + if not self.raise_all and exc_type in self.exceptions: return not self.handle(exc_type, exc_value, traceback) diff --git a/tests/files/HYP_LR.zip b/tests/files/HYP_LR.zip new file mode 100644 index 0000000000000000000000000000000000000000..971e57da33e3b7e096c2292dbc73e7bd9558e024 Binary files /dev/null and b/tests/files/HYP_LR.zip differ diff --git a/tests/testScenario.py b/tests/testScenario.py index b82f0022b78174b80db934065d0105e931091ae5..7756ae1d000ecb1666ae03cd73b89e8361962cf7 100644 --- a/tests/testScenario.py +++ b/tests/testScenario.py @@ -48,6 +48,10 @@ def test_scenario(): ws = APIRequest("GET", wss[0]["href"])["workspace"] assert ws["name"] == wss[0]["name"] + # + # Test DataStores. + # + # GET dataStores dss = APIRequest("GET", ws["dataStores"]["href"])["dataStores"] @@ -58,7 +62,7 @@ def test_scenario(): name, title = "testDS1", "test datastore 1" _, r = APIRequest("POST", ws["dataStores"]["href"], {"dataStore":{"name":name, "title":title}}, get_response=True) - ds_link = r.getheader("Location") + ds_link = r.getheader("Location").split(".", 1)[0] ds = APIRequest("GET", ds_link)["dataStore"] assert ds["name"] == name @@ -85,16 +89,108 @@ def test_scenario(): encode=None, content_type="application/zip") ds = APIRequest("GET", ds_link)["dataStore"] - assert ds["connectionParameters"]["url"] == "file:/workspaces/%s/datastores/%s/timezones.shp" % (ws["name"], ds["name"]) + assert ds["connectionParameters"]["url"] == "file:workspaces/%s/datastores/%s/timezones.shp" % (ws["name"], ds["name"]) - # POST a featuretype + # POST a featuretype and GET it - name, title = "testFT1", "test feature type 1" + name, title = "timezones", "test feature type 1" _, r = APIRequest("POST", ds["href"], {"featureType":{"name":name, "title":title}}, get_response=True) - ft_link = r.getheader("Location") + ft_link = r.getheader("Location").split(".", 1)[0] ft = APIRequest("GET", ft_link)["featureType"] assert ft["name"] == name - assert ds["title"] == title + assert ft["title"] == title + + # PUT a featuretype + + ft["title"] = title.upper() + APIRequest("PUT", ft_link, {"featureType":ft}) + + ft = APIRequest("GET", ft_link)["featureType"] + assert ft["title"] == title.upper() + + # DELETE stuff + + APIRequest("DELETE", ft_link) + fts = APIRequest("GET", ds_link + "/featuretypes")["featureTypes"] + assert len(fts) == 0 + + APIRequest("DELETE", ds_link) + dss = APIRequest("GET", ws["dataStores"]["href"])["dataStores"] + assert len(dss) == 0 + + + + # + # Test CoverageStores. + # + + # GET coverageStores + + css = APIRequest("GET", ws["coverageStores"]["href"])["coverageStores"] + assert len(css) == 0 + + # POST a coverageStore and GET it + + name, title = "testCS1", "test coverageStore 1" + _, r = APIRequest("POST", ws["coverageStores"]["href"], {"coverageStore":{"name":name, "title":title}}, + get_response=True) + cs_link = r.getheader("Location").split(".", 1)[0] + + cs = APIRequest("GET", cs_link)["coverageStore"] + assert cs["name"] == name + assert cs["title"] == title + + # PUT a coverageStore + + cs["title"] = title.upper() + del cs["href"] + APIRequest("PUT", cs_link, {"coverageStore":cs}) + + cs = APIRequest("GET", cs_link)["coverageStore"] + assert cs["title"] == title.upper() + + # GET coverages + + fts = APIRequest("GET", cs["href"])["coverages"] + assert len(fts) == 0 + + + # PUT file, and check if coverageStore is updated. + + APIRequest("PUT", cs_link + "/file.tif", open("./files/HYP_LR.zip", "rb"), + encode=None, content_type="application/zip") + + cs = APIRequest("GET", cs_link)["coverageStore"] + assert cs["connectionParameters"]["url"] == "file:workspaces/%s/coveragestores/%s/HYP_LR/HYP_LR.tif" % (ws["name"], cs["name"]) + + # POST a coverage and GET it + + name, title = "HYP_LR", "test coverage 1" + _, r = APIRequest("POST", cs["href"], {"coverage":{"name":name, "title":title}}, + get_response=True) + ft_link = r.getheader("Location").split(".", 1)[0] + + ft = APIRequest("GET", ft_link)["coverage"] + assert ft["name"] == name + assert ft["title"] == title + + # PUT a coverage + + ft["title"] = title.upper() + APIRequest("PUT", ft_link, {"coverage":ft}) + + ft = APIRequest("GET", ft_link)["coverage"] + assert ft["title"] == title.upper() + + # DELETE stuff + + APIRequest("DELETE", ft_link) + fts = APIRequest("GET", cs_link + "/coverages")["coverages"] + assert len(fts) == 0 + + APIRequest("DELETE", cs_link) + css = APIRequest("GET", ws["coverageStores"]["href"])["coverageStores"] + assert len(css) == 0