We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Permissions for Creating Folders?
Hello,

I'm working on HydroShare export functionality for Model My Watershed. I am now able to successfully create resources and add files to them (for example: [66ce7b01a1e7490383e37a407979631c](https://beta.hydroshare.org/resource/66ce7b01a1e7490383e37a407979631c/)). So far I am using `hs_restclient`'s `createResource` and `addResourceFile` methods, which work very well. However, if I try to create a folder with `createResourceFolder`, I get a HydroShareNotAuthorized exception:

```
HydroShareNotAuthorized at /export/hydroshare/
Not authorized to perform PUT on https://beta.hydroshare.org/hsapi/resource/3c741c66702649f689aedcd580e23f9b/folders/area-of-interest.
```

I am able to create folders using the HydroShare UI, so it seems I just don't have the permission in the REST API, which seems odd.

Here's what my code looks like (messy, early draft):

```python
resource = hs.createResource(
    'CompositeResource',
    params.get('title', project.name),
    abstract=params.get('abstract', ''),
    keywords=('mmw', 'model-my-watershed')
)

aoi_folder = 'area-of-interest'
hs.createResourceFolder(resource, pathname=aoi_folder)

# AoI GeoJSON
aoi_geojson = GEOSGeometry(project.area_of_interest).geojson
aoi_file = StringIO.StringIO()
aoi_file.write(aoi_geojson)
hs.addResourceFile(resource, aoi_file,
                   '{}/{}'.format(aoi_folder, 'shape.geojson'))

# AoI Shapefile
aoi_json = json.loads(aoi_geojson)
crs = {'no_defs': True, 'proj': 'longlat',
       'ellps': 'WGS84', 'datum': 'WGS84'}
schema = {'geometry': aoi_json['type'], 'properties': {}}
with fiona.open('/tmp/{}.shp'.format(resource), 'w',
                driver='ESRI Shapefile',
                crs=crs, schema=schema) as shapefile:
    shapefile.write({'geometry': aoi_json, 'properties': {}})

for ext in SHAPEFILE_EXTENSIONS:
    with open('/tmp/{}.{}'.format(resource, ext)) as shapefile:
        hs.addResourceFile(resource, shapefile,
                           '{}/shape.{}'.format(aoi_folder, ext))
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `hs_composite_resource/models.py`
Content:
```
1 import os
2 
3 from mezzanine.pages.page_processors import processor_for
4 
5 from hs_core.models import BaseResource, ResourceManager, resource_processor
6 
7 from hs_file_types.models import GenericLogicalFile
8 
9 
10 class CompositeResource(BaseResource):
11     objects = ResourceManager("CompositeResource")
12 
13     class Meta:
14         verbose_name = 'Composite Resource'
15         proxy = True
16 
17     @property
18     def can_be_public_or_discoverable(self):
19         # resource level metadata check
20         if not super(CompositeResource, self).can_be_public_or_discoverable:
21             return False
22 
23         # filetype level metadata check
24         for lf in self.logical_files:
25             if not lf.metadata.has_all_required_elements():
26                 return False
27 
28         return True
29 
30     def set_default_logical_file(self):
31         """sets an instance of GenericLogicalFile to any resource file objects of this instance
32         of the resource that is not already associated with a logical file. """
33 
34         for res_file in self.files.all():
35             if not res_file.has_logical_file:
36                 logical_file = GenericLogicalFile.create()
37                 res_file.logical_file_content_object = logical_file
38                 res_file.save()
39 
40     @property
41     def supports_logical_file(self):
42         """ if this resource allows associating resource file objects with logical file"""
43         return True
44 
45     def get_metadata_xml(self, pretty_print=True, include_format_elements=True):
46         from lxml import etree
47 
48         # get resource level core metadata as xml string
49         # for composite resource we don't want the format elements at the resource level
50         # as they are included at the file level xml node
51         xml_string = super(CompositeResource, self).get_metadata_xml(pretty_print=False,
52                                                                      include_format_elements=False)
53         # add file type metadata xml
54 
55         # create an etree xml object
56         RDF_ROOT = etree.fromstring(xml_string)
57 
58         # get root 'Description' element that contains all other elements
59         container = RDF_ROOT.find('rdf:Description', namespaces=self.metadata.NAMESPACES)
60 
61         for lf in self.logical_files:
62             lf.metadata.add_to_xml_container(container)
63 
64         return etree.tostring(RDF_ROOT, pretty_print=pretty_print)
65 
66     def supports_folder_creation(self, folder_full_path):
67         """this checks if it is allowed to create a folder at the specified path"""
68 
69         if __debug__:
70             assert(folder_full_path.startswith(self.file_path))
71 
72         # determine containing folder
73         if "/" in folder_full_path:
74             path_to_check, _ = os.path.split(folder_full_path)
75         else:
76             path_to_check = folder_full_path
77 
78         if path_to_check != self.file_path:
79             res_file_objs = [res_file_obj for res_file_obj in self.files.all() if
80                              res_file_obj.dir_path == path_to_check]
81 
82             for res_file_obj in res_file_objs:
83                 if not res_file_obj.logical_file.supports_resource_file_rename or \
84                         not res_file_obj.logical_file.supports_resource_file_move:
85                     return False
86 
87         return True
88 
89     def supports_rename_path(self, src_full_path, tgt_full_path):
90         """checks if file/folder rename/move is allowed"""
91 
92         if __debug__:
93             assert(src_full_path.startswith(self.file_path))
94             assert(tgt_full_path.startswith(self.file_path))
95 
96         istorage = self.get_irods_storage()
97         tgt_file_dir = os.path.dirname(tgt_full_path)
98         src_file_dir = os.path.dirname(src_full_path)
99 
100         def check_directory():
101             path_to_check = ''
102             if istorage.exists(tgt_file_dir):
103                 path_to_check = tgt_file_dir
104             else:
105                 if tgt_file_dir.startswith(src_file_dir):
106                     path_to_check = src_file_dir
107 
108             if path_to_check and not path_to_check.endswith("data/contents"):
109                 # it is not the base directory - it must be a directory under base dir
110                 res_file_objs = [res_file_obj for res_file_obj in self.files.all() if
111                                  res_file_obj.dir_path == path_to_check]
112 
113                 for res_file_obj in res_file_objs:
114                     if not res_file_obj.logical_file.supports_resource_file_rename or \
115                             not res_file_obj.logical_file.supports_resource_file_move:
116                         return False
117             return True
118 
119         res_file_objs = [res_file_obj for res_file_obj in self.files.all() if
120                          res_file_obj.full_path == src_full_path]
121 
122         if res_file_objs:
123             res_file_obj = res_file_objs[0]
124             # src_full_path contains file name
125             if not res_file_obj.logical_file.supports_resource_file_rename or \
126                     not res_file_obj.logical_file.supports_resource_file_move:
127                 return False
128 
129             # check if the target directory allows stuff to be moved there
130             return check_directory()
131         else:
132             # src_full_path is a folder path without file name
133             # tgt_full_path also must be a folder path without file name
134             # check that if the target folder contains any files and if any of those files
135             # allow moving stuff there
136             return check_directory()
137 
138     def can_add_files(self, target_full_path):
139         """
140         checks if file(s) can be uploaded to the specified *target_full_path*
141         :param target_full_path: full folder path name where file needs to be uploaded to
142         :return: True or False
143         """
144         istorage = self.get_irods_storage()
145         if istorage.exists(target_full_path):
146             path_to_check = target_full_path
147         else:
148             return False
149 
150         if not path_to_check.endswith("data/contents"):
151             # it is not the base directory - it must be a directory under base dir
152             res_file_objs = [res_file_obj for res_file_obj in self.files.all() if
153                              res_file_obj.dir_path == path_to_check]
154 
155             for res_file_obj in res_file_objs:
156                 if not res_file_obj.logical_file.supports_resource_file_add:
157                     return False
158         return True
159 
160     def supports_zip(self, folder_to_zip):
161         """check if the given folder can be zipped or not"""
162 
163         # find all the resource files in the folder to be zipped
164         # this is being passed both qualified and unqualified paths!
165         full_path = folder_to_zip
166         if not full_path.startswith(self.file_path):
167             full_path = os.path.join(self.file_path, full_path)
168 
169         if self.is_federated:
170             res_file_objects = self.files.filter(
171                 object_id=self.id,
172                 fed_resource_file__startswith=full_path).all()
173         else:
174             res_file_objects = self.files.filter(object_id=self.id,
175                                                  resource_file__startswith=full_path).all()
176 
177         # check any logical file associated with the resource file supports zip functionality
178         for res_file in res_file_objects:
179             if not res_file.logical_file.supports_zip:
180                 return False
181 
182         return True
183 
184     def supports_delete_folder_on_zip(self, original_folder):
185         """check if the specified folder can be deleted at the end of zipping that folder"""
186 
187         # find all the resource files in the folder to be deleted
188         # this is being passed both qualified and unqualified paths!
189         full_path = original_folder
190         if not full_path.startswith(self.file_path):
191             full_path = os.path.join(self.file_path, full_path)
192 
193         if self.is_federated:
194             res_file_objects = self.files.filter(
195                 object_id=self.id,
196                 fed_resource_file__startswith=full_path).all()
197         else:
198             res_file_objects = self.files.filter(
199                 object_id=self.id,
200                 resource_file__startswith=full_path).all()
201 
202         # check any logical file associated with the resource file supports deleting the folder
203         # after its zipped
204         for res_file in res_file_objects:
205             if not res_file.logical_file.supports_delete_folder_on_zip:
206                 return False
207 
208         return True
209 
210     def get_missing_file_type_metadata_info(self):
211         # this is used in page pre-processor to build the context
212         # so that the landing page can show what metadata items are missing for each logical file
213         metadata_missing_info = []
214         for lfo in self.logical_files:
215             if not lfo.metadata.has_all_required_elements():
216                 file_path = lfo.files.first().short_path
217                 missing_elements = lfo.metadata.get_required_missing_elements()
218                 metadata_missing_info.append({'file_path': file_path,
219                                               'missing_elements': missing_elements})
220         return metadata_missing_info
221 
222 # this would allow us to pick up additional form elements for the template before the template
223 # is displayed
224 processor_for(CompositeResource)(resource_processor)
225
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/hs_composite_resource/models.py b/hs_composite_resource/models.py
--- a/hs_composite_resource/models.py
+++ b/hs_composite_resource/models.py
@@ -37,6 +37,11 @@
                 res_file.logical_file_content_object = logical_file
                 res_file.save()
 
+    @property
+    def supports_folders(self):
+        """ allow folders for CompositeResources """
+        return True
+
     @property
     def supports_logical_file(self):
         """ if this resource allows associating resource file objects with logical file"""

{"golden_diff": "diff --git a/hs_composite_resource/models.py b/hs_composite_resource/models.py\n--- a/hs_composite_resource/models.py\n+++ b/hs_composite_resource/models.py\n@@ -37,6 +37,11 @@\n                 res_file.logical_file_content_object = logical_file\n                 res_file.save()\n \n+    @property\n+    def supports_folders(self):\n+        \"\"\" allow folders for CompositeResources \"\"\"\n+        return True\n+\n     @property\n     def supports_logical_file(self):\n         \"\"\" if this resource allows associating resource file objects with logical file\"\"\"\n", "issue": "Permissions for Creating Folders?\nHello,\r\n\r\nI'm working on HydroShare export functionality for Model My Watershed. I am now able to successfully create resources and add files to them (for example: [66ce7b01a1e7490383e37a407979631c](https://beta.hydroshare.org/resource/66ce7b01a1e7490383e37a407979631c/)). So far I am using `hs_restclient`'s `createResource` and `addResourceFile` methods, which work very well. However, if I try to create a folder with `createResourceFolder`, I get a HydroShareNotAuthorized exception:\r\n\r\n```\r\nHydroShareNotAuthorized at /export/hydroshare/\r\nNot authorized to perform PUT on https://beta.hydroshare.org/hsapi/resource/3c741c66702649f689aedcd580e23f9b/folders/area-of-interest.\r\n```\r\n\r\nI am able to create folders using the HydroShare UI, so it seems I just don't have the permission in the REST API, which seems odd.\r\n\r\nHere's what my code looks like (messy, early draft):\r\n\r\n```python\r\nresource = hs.createResource(\r\n    'CompositeResource',\r\n    params.get('title', project.name),\r\n    abstract=params.get('abstract', ''),\r\n    keywords=('mmw', 'model-my-watershed')\r\n)\r\n\r\naoi_folder = 'area-of-interest'\r\nhs.createResourceFolder(resource, pathname=aoi_folder)\r\n\r\n# AoI GeoJSON\r\naoi_geojson = GEOSGeometry(project.area_of_interest).geojson\r\naoi_file = StringIO.StringIO()\r\naoi_file.write(aoi_geojson)\r\nhs.addResourceFile(resource, aoi_file,\r\n                   '{}/{}'.format(aoi_folder, 'shape.geojson'))\r\n\r\n# AoI Shapefile\r\naoi_json = json.loads(aoi_geojson)\r\ncrs = {'no_defs': True, 'proj': 'longlat',\r\n       'ellps': 'WGS84', 'datum': 'WGS84'}\r\nschema = {'geometry': aoi_json['type'], 'properties': {}}\r\nwith fiona.open('/tmp/{}.shp'.format(resource), 'w',\r\n                driver='ESRI Shapefile',\r\n                crs=crs, schema=schema) as shapefile:\r\n    shapefile.write({'geometry': aoi_json, 'properties': {}})\r\n\r\nfor ext in SHAPEFILE_EXTENSIONS:\r\n    with open('/tmp/{}.{}'.format(resource, ext)) as shapefile:\r\n        hs.addResourceFile(resource, shapefile,\r\n                           '{}/shape.{}'.format(aoi_folder, ext))\r\n```\n", "before_files": [{"content": "import os\n\nfrom mezzanine.pages.page_processors import processor_for\n\nfrom hs_core.models import BaseResource, ResourceManager, resource_processor\n\nfrom hs_file_types.models import GenericLogicalFile\n\n\nclass CompositeResource(BaseResource):\n    objects = ResourceManager(\"CompositeResource\")\n\n    class Meta:\n        verbose_name = 'Composite Resource'\n        proxy = True\n\n    @property\n    def can_be_public_or_discoverable(self):\n        # resource level metadata check\n        if not super(CompositeResource, self).can_be_public_or_discoverable:\n            return False\n\n        # filetype level metadata check\n        for lf in self.logical_files:\n            if not lf.metadata.has_all_required_elements():\n                return False\n\n        return True\n\n    def set_default_logical_file(self):\n        \"\"\"sets an instance of GenericLogicalFile to any resource file objects of this instance\n        of the resource that is not already associated with a logical file. \"\"\"\n\n        for res_file in self.files.all():\n            if not res_file.has_logical_file:\n                logical_file = GenericLogicalFile.create()\n                res_file.logical_file_content_object = logical_file\n                res_file.save()\n\n    @property\n    def supports_logical_file(self):\n        \"\"\" if this resource allows associating resource file objects with logical file\"\"\"\n        return True\n\n    def get_metadata_xml(self, pretty_print=True, include_format_elements=True):\n        from lxml import etree\n\n        # get resource level core metadata as xml string\n        # for composite resource we don't want the format elements at the resource level\n        # as they are included at the file level xml node\n        xml_string = super(CompositeResource, self).get_metadata_xml(pretty_print=False,\n                                                                     include_format_elements=False)\n        # add file type metadata xml\n\n        # create an etree xml object\n        RDF_ROOT = etree.fromstring(xml_string)\n\n        # get root 'Description' element that contains all other elements\n        container = RDF_ROOT.find('rdf:Description', namespaces=self.metadata.NAMESPACES)\n\n        for lf in self.logical_files:\n            lf.metadata.add_to_xml_container(container)\n\n        return etree.tostring(RDF_ROOT, pretty_print=pretty_print)\n\n    def supports_folder_creation(self, folder_full_path):\n        \"\"\"this checks if it is allowed to create a folder at the specified path\"\"\"\n\n        if __debug__:\n            assert(folder_full_path.startswith(self.file_path))\n\n        # determine containing folder\n        if \"/\" in folder_full_path:\n            path_to_check, _ = os.path.split(folder_full_path)\n        else:\n            path_to_check = folder_full_path\n\n        if path_to_check != self.file_path:\n            res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                             res_file_obj.dir_path == path_to_check]\n\n            for res_file_obj in res_file_objs:\n                if not res_file_obj.logical_file.supports_resource_file_rename or \\\n                        not res_file_obj.logical_file.supports_resource_file_move:\n                    return False\n\n        return True\n\n    def supports_rename_path(self, src_full_path, tgt_full_path):\n        \"\"\"checks if file/folder rename/move is allowed\"\"\"\n\n        if __debug__:\n            assert(src_full_path.startswith(self.file_path))\n            assert(tgt_full_path.startswith(self.file_path))\n\n        istorage = self.get_irods_storage()\n        tgt_file_dir = os.path.dirname(tgt_full_path)\n        src_file_dir = os.path.dirname(src_full_path)\n\n        def check_directory():\n            path_to_check = ''\n            if istorage.exists(tgt_file_dir):\n                path_to_check = tgt_file_dir\n            else:\n                if tgt_file_dir.startswith(src_file_dir):\n                    path_to_check = src_file_dir\n\n            if path_to_check and not path_to_check.endswith(\"data/contents\"):\n                # it is not the base directory - it must be a directory under base dir\n                res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                                 res_file_obj.dir_path == path_to_check]\n\n                for res_file_obj in res_file_objs:\n                    if not res_file_obj.logical_file.supports_resource_file_rename or \\\n                            not res_file_obj.logical_file.supports_resource_file_move:\n                        return False\n            return True\n\n        res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                         res_file_obj.full_path == src_full_path]\n\n        if res_file_objs:\n            res_file_obj = res_file_objs[0]\n            # src_full_path contains file name\n            if not res_file_obj.logical_file.supports_resource_file_rename or \\\n                    not res_file_obj.logical_file.supports_resource_file_move:\n                return False\n\n            # check if the target directory allows stuff to be moved there\n            return check_directory()\n        else:\n            # src_full_path is a folder path without file name\n            # tgt_full_path also must be a folder path without file name\n            # check that if the target folder contains any files and if any of those files\n            # allow moving stuff there\n            return check_directory()\n\n    def can_add_files(self, target_full_path):\n        \"\"\"\n        checks if file(s) can be uploaded to the specified *target_full_path*\n        :param target_full_path: full folder path name where file needs to be uploaded to\n        :return: True or False\n        \"\"\"\n        istorage = self.get_irods_storage()\n        if istorage.exists(target_full_path):\n            path_to_check = target_full_path\n        else:\n            return False\n\n        if not path_to_check.endswith(\"data/contents\"):\n            # it is not the base directory - it must be a directory under base dir\n            res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                             res_file_obj.dir_path == path_to_check]\n\n            for res_file_obj in res_file_objs:\n                if not res_file_obj.logical_file.supports_resource_file_add:\n                    return False\n        return True\n\n    def supports_zip(self, folder_to_zip):\n        \"\"\"check if the given folder can be zipped or not\"\"\"\n\n        # find all the resource files in the folder to be zipped\n        # this is being passed both qualified and unqualified paths!\n        full_path = folder_to_zip\n        if not full_path.startswith(self.file_path):\n            full_path = os.path.join(self.file_path, full_path)\n\n        if self.is_federated:\n            res_file_objects = self.files.filter(\n                object_id=self.id,\n                fed_resource_file__startswith=full_path).all()\n        else:\n            res_file_objects = self.files.filter(object_id=self.id,\n                                                 resource_file__startswith=full_path).all()\n\n        # check any logical file associated with the resource file supports zip functionality\n        for res_file in res_file_objects:\n            if not res_file.logical_file.supports_zip:\n                return False\n\n        return True\n\n    def supports_delete_folder_on_zip(self, original_folder):\n        \"\"\"check if the specified folder can be deleted at the end of zipping that folder\"\"\"\n\n        # find all the resource files in the folder to be deleted\n        # this is being passed both qualified and unqualified paths!\n        full_path = original_folder\n        if not full_path.startswith(self.file_path):\n            full_path = os.path.join(self.file_path, full_path)\n\n        if self.is_federated:\n            res_file_objects = self.files.filter(\n                object_id=self.id,\n                fed_resource_file__startswith=full_path).all()\n        else:\n            res_file_objects = self.files.filter(\n                object_id=self.id,\n                resource_file__startswith=full_path).all()\n\n        # check any logical file associated with the resource file supports deleting the folder\n        # after its zipped\n        for res_file in res_file_objects:\n            if not res_file.logical_file.supports_delete_folder_on_zip:\n                return False\n\n        return True\n\n    def get_missing_file_type_metadata_info(self):\n        # this is used in page pre-processor to build the context\n        # so that the landing page can show what metadata items are missing for each logical file\n        metadata_missing_info = []\n        for lfo in self.logical_files:\n            if not lfo.metadata.has_all_required_elements():\n                file_path = lfo.files.first().short_path\n                missing_elements = lfo.metadata.get_required_missing_elements()\n                metadata_missing_info.append({'file_path': file_path,\n                                              'missing_elements': missing_elements})\n        return metadata_missing_info\n\n# this would allow us to pick up additional form elements for the template before the template\n# is displayed\nprocessor_for(CompositeResource)(resource_processor)\n", "path": "hs_composite_resource/models.py"}], "after_files": [{"content": "import os\n\nfrom mezzanine.pages.page_processors import processor_for\n\nfrom hs_core.models import BaseResource, ResourceManager, resource_processor\n\nfrom hs_file_types.models import GenericLogicalFile\n\n\nclass CompositeResource(BaseResource):\n    objects = ResourceManager(\"CompositeResource\")\n\n    class Meta:\n        verbose_name = 'Composite Resource'\n        proxy = True\n\n    @property\n    def can_be_public_or_discoverable(self):\n        # resource level metadata check\n        if not super(CompositeResource, self).can_be_public_or_discoverable:\n            return False\n\n        # filetype level metadata check\n        for lf in self.logical_files:\n            if not lf.metadata.has_all_required_elements():\n                return False\n\n        return True\n\n    def set_default_logical_file(self):\n        \"\"\"sets an instance of GenericLogicalFile to any resource file objects of this instance\n        of the resource that is not already associated with a logical file. \"\"\"\n\n        for res_file in self.files.all():\n            if not res_file.has_logical_file:\n                logical_file = GenericLogicalFile.create()\n                res_file.logical_file_content_object = logical_file\n                res_file.save()\n\n    @property\n    def supports_folders(self):\n        \"\"\" allow folders for CompositeResources \"\"\"\n        return True\n\n    @property\n    def supports_logical_file(self):\n        \"\"\" if this resource allows associating resource file objects with logical file\"\"\"\n        return True\n\n    def get_metadata_xml(self, pretty_print=True, include_format_elements=True):\n        from lxml import etree\n\n        # get resource level core metadata as xml string\n        # for composite resource we don't want the format elements at the resource level\n        # as they are included at the file level xml node\n        xml_string = super(CompositeResource, self).get_metadata_xml(pretty_print=False,\n                                                                     include_format_elements=False)\n        # add file type metadata xml\n\n        # create an etree xml object\n        RDF_ROOT = etree.fromstring(xml_string)\n\n        # get root 'Description' element that contains all other elements\n        container = RDF_ROOT.find('rdf:Description', namespaces=self.metadata.NAMESPACES)\n\n        for lf in self.logical_files:\n            lf.metadata.add_to_xml_container(container)\n\n        return etree.tostring(RDF_ROOT, pretty_print=pretty_print)\n\n    def supports_folder_creation(self, folder_full_path):\n        \"\"\"this checks if it is allowed to create a folder at the specified path\"\"\"\n\n        if __debug__:\n            assert(folder_full_path.startswith(self.file_path))\n\n        # determine containing folder\n        if \"/\" in folder_full_path:\n            path_to_check, _ = os.path.split(folder_full_path)\n        else:\n            path_to_check = folder_full_path\n\n        if path_to_check != self.file_path:\n            res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                             res_file_obj.dir_path == path_to_check]\n\n            for res_file_obj in res_file_objs:\n                if not res_file_obj.logical_file.supports_resource_file_rename or \\\n                        not res_file_obj.logical_file.supports_resource_file_move:\n                    return False\n\n        return True\n\n    def supports_rename_path(self, src_full_path, tgt_full_path):\n        \"\"\"checks if file/folder rename/move is allowed\"\"\"\n\n        if __debug__:\n            assert(src_full_path.startswith(self.file_path))\n            assert(tgt_full_path.startswith(self.file_path))\n\n        istorage = self.get_irods_storage()\n        tgt_file_dir = os.path.dirname(tgt_full_path)\n        src_file_dir = os.path.dirname(src_full_path)\n\n        def check_directory():\n            path_to_check = ''\n            if istorage.exists(tgt_file_dir):\n                path_to_check = tgt_file_dir\n            else:\n                if tgt_file_dir.startswith(src_file_dir):\n                    path_to_check = src_file_dir\n\n            if path_to_check and not path_to_check.endswith(\"data/contents\"):\n                # it is not the base directory - it must be a directory under base dir\n                res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                                 res_file_obj.dir_path == path_to_check]\n\n                for res_file_obj in res_file_objs:\n                    if not res_file_obj.logical_file.supports_resource_file_rename or \\\n                            not res_file_obj.logical_file.supports_resource_file_move:\n                        return False\n            return True\n\n        res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                         res_file_obj.full_path == src_full_path]\n\n        if res_file_objs:\n            res_file_obj = res_file_objs[0]\n            # src_full_path contains file name\n            if not res_file_obj.logical_file.supports_resource_file_rename or \\\n                    not res_file_obj.logical_file.supports_resource_file_move:\n                return False\n\n            # check if the target directory allows stuff to be moved there\n            return check_directory()\n        else:\n            # src_full_path is a folder path without file name\n            # tgt_full_path also must be a folder path without file name\n            # check that if the target folder contains any files and if any of those files\n            # allow moving stuff there\n            return check_directory()\n\n    def can_add_files(self, target_full_path):\n        \"\"\"\n        checks if file(s) can be uploaded to the specified *target_full_path*\n        :param target_full_path: full folder path name where file needs to be uploaded to\n        :return: True or False\n        \"\"\"\n        istorage = self.get_irods_storage()\n        if istorage.exists(target_full_path):\n            path_to_check = target_full_path\n        else:\n            return False\n\n        if not path_to_check.endswith(\"data/contents\"):\n            # it is not the base directory - it must be a directory under base dir\n            res_file_objs = [res_file_obj for res_file_obj in self.files.all() if\n                             res_file_obj.dir_path == path_to_check]\n\n            for res_file_obj in res_file_objs:\n                if not res_file_obj.logical_file.supports_resource_file_add:\n                    return False\n        return True\n\n    def supports_zip(self, folder_to_zip):\n        \"\"\"check if the given folder can be zipped or not\"\"\"\n\n        # find all the resource files in the folder to be zipped\n        # this is being passed both qualified and unqualified paths!\n        full_path = folder_to_zip\n        if not full_path.startswith(self.file_path):\n            full_path = os.path.join(self.file_path, full_path)\n\n        if self.is_federated:\n            res_file_objects = self.files.filter(\n                object_id=self.id,\n                fed_resource_file__startswith=full_path).all()\n        else:\n            res_file_objects = self.files.filter(object_id=self.id,\n                                                 resource_file__startswith=full_path).all()\n\n        # check any logical file associated with the resource file supports zip functionality\n        for res_file in res_file_objects:\n            if not res_file.logical_file.supports_zip:\n                return False\n\n        return True\n\n    def supports_delete_folder_on_zip(self, original_folder):\n        \"\"\"check if the specified folder can be deleted at the end of zipping that folder\"\"\"\n\n        # find all the resource files in the folder to be deleted\n        # this is being passed both qualified and unqualified paths!\n        full_path = original_folder\n        if not full_path.startswith(self.file_path):\n            full_path = os.path.join(self.file_path, full_path)\n\n        if self.is_federated:\n            res_file_objects = self.files.filter(\n                object_id=self.id,\n                fed_resource_file__startswith=full_path).all()\n        else:\n            res_file_objects = self.files.filter(\n                object_id=self.id,\n                resource_file__startswith=full_path).all()\n\n        # check any logical file associated with the resource file supports deleting the folder\n        # after its zipped\n        for res_file in res_file_objects:\n            if not res_file.logical_file.supports_delete_folder_on_zip:\n                return False\n\n        return True\n\n    def get_missing_file_type_metadata_info(self):\n        # this is used in page pre-processor to build the context\n        # so that the landing page can show what metadata items are missing for each logical file\n        metadata_missing_info = []\n        for lfo in self.logical_files:\n            if not lfo.metadata.has_all_required_elements():\n                file_path = lfo.files.first().short_path\n                missing_elements = lfo.metadata.get_required_missing_elements()\n                metadata_missing_info.append({'file_path': file_path,\n                                              'missing_elements': missing_elements})\n        return metadata_missing_info\n\n# this would allow us to pick up additional form elements for the template before the template\n# is displayed\nprocessor_for(CompositeResource)(resource_processor)\n", "path": "hs_composite_resource/models.py"}]}