Example #1
0
class Generator(object):
    def __init__(self,
                 verbose: bool,
                 output_dir=DEFAULT_OUTPUT_DIR,
                 module_name=DEFAULT_MODULE,
                 omit_exporter=False,
                 permissions=False,
                 permissions_store=False):
        self.parser = None
        self._resources = None
        self.verbose = verbose
        self.output_dir = output_dir
        self.module_name = module_name
        self._currentIO = None
        self.page_details = None
        self.omit_exporter = omit_exporter
        self.permissions = permissions or permissions_store
        self.permissions_store = permissions_store
        self.permissions_import = "permissions_store" \
            if permissions_store else "permissions"
        self._directories = set([])

    def load_specification(self, specification_path: str, spec_format: str):
        """
        This function will load the swagger specification using the swagger_parser.
        The function will automatically call the `_init_class_resources` after.
        :param specification_path: The path where the swagger specification is located.
        :param spec_format: The file format of the specification.
        """
        # If the swagger spec format is not specified explicitly, we try to
        # derive it from the specification path
        if not spec_format:
            filename = os.path.basename(specification_path)
            extension = filename.rsplit(".", 1)[-1]
            if extension in YAML_EXTENSIONS:
                spec_format = SPEC_YAML
            elif extension in JSON_EXTENSIONS:
                spec_format = SPEC_JSON
            else:
                raise RuntimeError("Could not infer specification format. Use "
                                   "--spec-format to specify it explicitly.")

        click.secho(f"Using spec format '{spec_format}'", fg="green")
        if spec_format == SPEC_YAML:
            with open(specification_path, "r") as f:
                self.parser = SwaggerParser(swagger_yaml=f)
        else:
            self.parser = SwaggerParser(swagger_path=specification_path)

        self._init_class_resources()

    def _get_definition_from_ref(self, definition: dict):
        """
        Get the swagger definition from a swagger reference declaration in the spec.
        :param definition: The definition containing the reference declaration.
        :return: The definition pointed to by the ref and the title
        """
        if "$ref" in definition:
            definition_name = \
                self.parser.get_definition_name_from_ref(definition["$ref"])

            title = definition_name.replace("_", " ").title().replace(" ", "")
            return self.parser.specification["definitions"][
                definition_name], title
        else:
            title = next(iter(definition)).replace(" ", " ").title().replace(
                " ", "")
            return definition, title

    def _get_parameter_from_ref(self, parameter: dict):
        """
        Get the parameter definition from a swagger reference declaration in the spec.
        :param parameter: The definition containing the reference declaration.
        :return: The parameter pointed to by the ref
        """
        # If the parameter is a reference, get the actual parameter.
        if "$ref" in parameter:
            ref = parameter["$ref"].split("/")[2]
            return self.parser.specification["parameters"][ref]
        else:
            return parameter

    def _get_parameter_definition(self):
        """
        Get the create/update object definition from the current parameters.
        :return: The definition desired.
        """
        for parameter in self._currentIO.get("parameters", []):
            param = self._get_parameter_from_ref(parameter)
            # Grab the body parameter as the create/update definition
            if param["in"] == "body":
                return self._get_definition_from_ref(
                    definition=param["schema"])
        return None

    def _get_related_field_permissions(self, field_name_resource: str):
        """
        Get the list permissions of a given related field resource.
        :param field_name_resource: The name of the related field resource.
        :return: The permissions found.
        """
        permissions = []
        list_path = self.parser.specification["paths"].get(
            f"/{field_name_resource}", None)
        if list_path:
            operation = list_path.get("get", None)
            if operation:
                permissions = operation.get("x-permissions", [])
        return permissions

    def _build_related_field(self, resource: str, name: str, _field: dict,
                             _property: dict):
        """
        Build out a related field
        :param resource: The name of the current resource.
        :param name: The name of the current field.
        :param _field: The field to be built out further as a related field.
        :param _property: The current property being looked at.
        :return: Tuple of the resultant field and if a related field or not.
        """
        related = False
        if "x-related-info" in _property:

            # Check and handle related info
            related_info = _property["x-related-info"]

            if related_info:
                model = related_info.get("model", False)
                # Check if related model is not set to None.
                if model is not None:
                    # Check and add permission imports if found
                    has_permissions = self._resources[resource][
                        "has_permission_fields"]
                    if self.permissions and not has_permissions:
                        self._resources[resource][
                            "has_permission_fields"] = True
                        self._resources[resource]["custom_imports"].update(
                            ["empty", self.permissions_import])
                    related = True
                    # If model didn't even exist then attempt to guess the model
                    # from the substring before the last "_".
                    if not model:
                        model = name.rsplit("_", 1)[0]
                    _field["label"] = model.replace("_", " ").title()
                    # If a custom base path has been given set the reference to it
                    # else attempt to get the plural of the given model.
                    if related_info.get("rest_resource_name",
                                        None) is not None:
                        reference = related_info["rest_resource_name"]
                    else:
                        reference = words.plural(model.replace("_", ""))
                    _field["reference"] = reference
                    # Add permissions of reference if using normal permission generation scheme.
                    if self.permissions and not self.permissions_store:
                        _field[
                            "permissions"] = self._get_related_field_permissions(
                                reference)

                    # Get the option text to be used in the Select input from the
                    # label field, else guess it from the current property name.
                    guess = name.rsplit("_", 1)[1]
                    label = related_info.get("label", None) or guess
                    _field["option_text"] = label

        return related, _field

    def _build_fields(self, resource: str, singular: str, properties: dict,
                      _input: bool, fields: list):
        """
        Build out fields for the given properties.
        :param resource: The current resource name.
        :param singular: The current singular resource name.
        :param properties: The properties to build the fields from.
        :param _input: Boolean signifying if input fields or not.
        :param fields: List of fields desired. If NONE all are allowed.
        :return: A tuple of fields and imports
        """
        _imports = set([])
        _fields = []
        required_properties = self._current_definition.get("required", [])
        page_details = self.page_details.get(singular, None)
        sortable = page_details.get("sortable_fields",
                                    []) if page_details else []
        for name, details in properties.items():

            # Check if in list of accepted fields
            if fields:
                if name not in fields:
                    continue

            # Handle possible reference definition.
            _property, title = self._get_definition_from_ref(details)

            # Not handling nested object definitions, yet, maybe.
            if "properties" in _property:
                continue
            read_only = _property.get("readOnly", False) and _input
            _type = _property.get("type", None)

            # Build field length object for strings and integer types
            length_values = {
                "minLength": _property.get("minLength", None),
                "maxLength": _property.get("maxLength", None),
                "minValue": _property.get("minimum", None),
                "maxValue": _property.get("maximum", None)
            } if _type in ["string", "integer"] else {}

            # Load imports
            one_value = False
            for key, value in length_values.items():
                if value:
                    one_value = True
                    _imports.add(key)

            _field = {
                "source": name,
                "type": _type,
                "required": name in required_properties,
                "sortable": name in sortable,
                "read_only": read_only,
                "length_values": length_values if one_value else {}
            }

            if read_only:
                _imports.add("DisabledInput")

            if _field["required"]:
                _imports.add("required")

            if _input:
                mapping = INPUT_COMPONENT_MAPPING
            else:
                mapping = FIELD_COMPONENT_MAPPING

            # Check for enum possibility.
            if "enum" in _property:
                _field["component"] = mapping["enum"]
                if _input:
                    _field["choices"] = _property["enum"]

            elif _field["type"] in mapping or _field["type"] in CUSTOM_IMPORTS:
                related, _field = self._build_related_field(
                    resource=resource,
                    name=name,
                    _field=_field,
                    _property=_property)

                if not related:
                    # Check if format overrides the component.
                    _format = _property.get("format", None)
                    if _format in mapping or _format in CUSTOM_IMPORTS:
                        _type = _format
                    # Check if a custom component exists for this _type
                    if _type in CUSTOM_IMPORTS and _type not in mapping:
                        _field["component"] = CUSTOM_IMPORTS[_type]["name"]
                    else:
                        _field["component"] = mapping[_type]
                else:
                    # Get relation component and the related component
                    _field["component"] = mapping["relation"]
                    relation = "SelectInput" if _input else mapping[_type]
                    _field["related_component"] = relation
                    _imports.add(relation)

            if _type in CUSTOM_IMPORTS and _type not in mapping:
                self._resources[resource]["custom_imports"].add(_type)
            else:
                # Add component to imports
                if "component" in _field:
                    _imports.add(_field["component"])

            _fields.append(_field)

        return _fields, _imports

    def _build_resource(self, resource: str, singular: str, method: str):
        """
        Build out a resource.
        :param resource: The name of the resource.
        :param singular: The singular name of the resource.
        :param method: The method to build out.
        """
        _input = method in ["create", "update"]
        properties = self._current_definition.get("properties", {})
        permissions = self._currentIO.get("x-permissions", [])

        if properties:
            # Build out fields for a resource.
            _fields, _imports = self._build_fields(resource=resource,
                                                   singular=singular,
                                                   properties=properties,
                                                   _input=_input,
                                                   fields=[])
            # Get responsive fields for a resource.
            page_details = self.page_details.get(singular, {})
            responsive_fields = page_details.get("responsive_fields", {})
            responsive_obj = {}

            for prop, field in responsive_fields.items():
                responsive_obj[prop] = {
                    "field": field,
                    "title": field.replace("_", " ").title()
                }
            if responsive_obj:
                _imports.update(["Responsive", "SimpleList"])
            self._resources[resource]["methods"][
                SUPPORTED_COMPONENTS[method]] = {
                    "fields": _fields,
                    "permissions": permissions,
                    "responsive_fields":
                    responsive_obj if method == "list" else None
                }
            current_imports = self._resources[resource]["imports"]
            self._resources[resource]["imports"] = current_imports.union(
                _imports)

    def _build_filters(self, resource: str):
        """
        Build out filters for a resource.
        :param resource: The current resource to build filters for.
        """
        filters = []
        filter_imports = set([])
        custom_imports = set([])
        for parameter in self._currentIO.get("parameters", []):
            param = self._get_parameter_from_ref(parameter)
            x_filter = param.get("x-filter", None)

            valid = all([
                param["in"] == "query",
                param["type"] in INPUT_COMPONENT_MAPPING or x_filter,
                "x-admin-exclude" not in param
            ])

            if valid:
                _type = param["type"]

                # Check if related model filter.
                relation = None
                related_info = param.get("x-related-info", None)
                if related_info:
                    _type = "relation"
                    relation = {
                        "component": "SelectInput",
                        "resource": related_info["rest_resource_name"],
                        "text": related_info.get("label", None)
                    }
                    filter_imports.add("SelectInput")

                # Add filter component
                if x_filter:
                    _type = x_filter["format"]
                    if x_filter.get("range", False):
                        _type = f"{_type}-range"

                if _type in INPUT_COMPONENT_MAPPING:
                    component = INPUT_COMPONENT_MAPPING[_type]
                    filter_imports.add(component)
                else:
                    component = CUSTOM_IMPORTS[_type]["name"]
                    custom_imports.add(_type)

                # Load any custom props
                props = CUSTOM_PROPS.get(_type, [])

                # Load Min and Max values of filter.
                _min = param.get("minLength", None)
                _max = param.get("maxLength", None)
                source = param["name"]
                label = source.replace("_", " ").title()

                if _min or _max:
                    self._resources[resource]["filter_lengths"][source] = {
                        "min_length": _min,
                        "max_length": _max
                    }

                array_validation = param["items"]["type"] \
                    if _type == "array" else None

                filters.append({
                    "source": source,
                    "label": label,
                    "title": label.replace(" ", ""),
                    "component": component,
                    "props": props,
                    "relation": relation,
                    "array": array_validation
                })

        self._resources[resource]["filters"] = {
            "filters":
            list(filters),
            "imports":
            list(filter_imports),
            "custom_imports":
            [CUSTOM_IMPORTS[_import] for _import in custom_imports]
        }

    def _build_in_lines(self, resource: str, singular: str, method: str):
        """
        Build out the given resource in lines for a resource.
        :param resource: The name of the current resource.
        :param singular: The singular name of the current resource.
        :param method: The current opertation method.
        """
        in_lines = []
        _input = method in ["create", "update"]
        if _input:
            mapping = INPUT_COMPONENT_MAPPING
        else:
            mapping = FIELD_COMPONENT_MAPPING

        for in_line in self.page_details[singular].get("inlines", []):
            has_permissions = self._resources[resource][
                "has_permission_fields"]
            if self.permissions and not has_permissions:
                self._resources[resource]["has_permission_fields"] = True
                self._resources[resource]["custom_imports"].update(
                    ["empty", self.permissions_import])
            model = in_line["model"]
            label = in_line.get("label", None)

            # If a custom base path has been given, use that as reference.
            ref = in_line.get("rest_resource_name", None)
            reference = ref or words.plural(model.replace("_", ""))
            permissions = []
            if self.permissions and not self.permissions_store:
                permissions = self._get_related_field_permissions(reference)

            fields = in_line.get("fields", None)
            many_field = {
                "label": label or model.replace("_", " ").title(),
                "reference": reference,
                "target": in_line["key"],
                "component": mapping["many"],
                "permissions": permissions
            }

            self._resources[resource]["imports"].add(many_field["component"])
            _def = self.parser.specification["definitions"][in_line["model"]]
            properties = _def.get("properties", {})
            many_field["fields"], _imports = self._build_fields(
                resource=resource,
                singular=singular,
                properties=properties,
                _input=False,
                fields=fields)
            in_lines.append(many_field)
            self._resources[resource]["imports"].update(_imports)

        self._resources[resource]["methods"][
            SUPPORTED_COMPONENTS[method]]["inlines"] = in_lines

    def _init_class_resources(self):
        """
        Initialize the class resources object.
        """
        self._resources = {}
        self.page_details = self.parser.specification.get(
            "x-detail-page-definitions", {})

        for path, verbs in self.parser.specification["paths"].items():
            for verb, io in verbs.items():
                self._currentIO = io
                # Ignore top level parameter definition (not a path).
                if verb == "parameters":
                    continue
                else:
                    # Check if operation id is a valid operation.
                    operation_id = io.get("operationId", "")
                    exclude = io.get("x-admin-exclude", False)
                    valid_operation = any([
                        operation in operation_id
                        for operation in VALID_OPERATIONS
                    ])
                    if not operation_id or not valid_operation or exclude:
                        continue

                singular, op = operation_id.rsplit("_", 1)
                plural = path[1:].split("/")[0]
                details = VALID_OPERATIONS.get(op, None)
                if details:
                    if plural not in self._resources:
                        self._resources[plural] = {
                            "singular": singular,
                            "imports": set([]),
                            "methods": {},
                            "filter_lengths": {},
                            "has_permission_fields": False,
                            "custom_imports": set([])
                        }
                    self._resources[plural]["imports"].update(
                        details["imports"])

                    # Special additions for certain operation types.
                    if op == "list":
                        self._current_definition, title = self._get_definition_from_ref(
                            definition=io['responses']['200']['schema']
                            ['items'])
                        self._resources[plural]["title"] = title
                        self._build_filters(resource=plural)
                    elif op == "read":
                        self._current_definition, title = self._get_definition_from_ref(
                            definition=io['responses']['200']['schema'])
                        self._resources[plural]["title"] = title
                    elif op in ["create", "update"]:
                        if op == "update":
                            self._resources[plural]["custom_imports"].add(
                                self.permissions_import)
                        self._current_definition, title = self._get_parameter_definition(
                        )
                    elif op == "delete":
                        self._resources[plural]["custom_imports"].add(
                            self.permissions_import)
                        self._current_definition = None
                        if self.permissions:
                            permissions = io.get("x-permissions", [])
                        else:
                            permissions = None
                        self._resources[plural]["methods"]["remove"] = {
                            "permissions": permissions
                        }

                    # Build out the current resource if a definition is found.
                    if self._current_definition:
                        self._build_resource(resource=plural,
                                             singular=singular,
                                             method=op)

                        # Build in lines if existing page definition.
                        in_lines = all([
                            op in ["update", "read"], singular
                            in self.page_details
                        ])

                        if in_lines:
                            self._build_in_lines(resource=plural,
                                                 singular=singular,
                                                 method=op)

        for resource in self._resources.keys():
            custom_imports = list(self._resources[resource]["custom_imports"])
            custom_imports = [
                CUSTOM_IMPORTS[_import] for _import in custom_imports
            ]
            self._resources[resource]["custom_imports"] = custom_imports

    @staticmethod
    def generate_js_file(filename: str, context: dict):
        """
        Generate a js file from the given specification.
        :param filename: The name of the template file.
        :param context: Context to be passed.
        :return: str
        """
        return render_to_string(filename, context)

    @staticmethod
    def add_additional_file(filename: str):
        """
        Add an additional file, that does not require context,
        to the generated admin.
        :return: str
        """
        return render_to_string(filename, {})

    def create_and_generate_file(self,
                                 _dir: str,
                                 filename: str,
                                 context: dict,
                                 source=None):
        """
        Create a file of the given name and context.
        :param _dir: The output directory.
        :param filename: The name of the file to be created.
        :param context: The context for jinja.
        :param source: Alternative source file for the template.
        """
        click.secho(f"Generating {filename}.js file...", fg="green")
        with open(os.path.join(_dir, f"{filename}.js"), "w") as f:
            data = self.generate_js_file(filename=f"{source or filename}.js",
                                         context=context)
            f.write(data)
            if self.verbose:
                print(data)

    def create_additional_files(self, additional_files: dict):
        for _dir, files in additional_files.items():
            if _dir != "root":
                path_dir = f"{self.output_dir}/{_dir}"
                if not os.path.exists(path_dir):
                    os.makedirs(path_dir)
            else:
                path_dir = self.output_dir
            for _file in files:
                click.secho(f"Adding {_file} file...", fg="cyan")
                with open(os.path.join(path_dir, _file), "w") as f:
                    data = self.add_additional_file(_file)
                    f.write(data)
                    if self.verbose:
                        print(data)

    def get_and_create_directory(self, _dir_name: str):
        """
        Gets the full directory and creates it if it does not exist.
        :param _dir_name: The sub directory to get and create if needed.
        :return: string of the full directory
        """
        full_dir = f"{self.output_dir}/{_dir_name}"
        if _dir_name not in self._directories:
            os.makedirs(full_dir)
            self._directories.add(_dir_name)
        return full_dir

    def admin_generation(self):
        click.secho("Generating main JS component file...", fg="blue")
        self.create_and_generate_file(_dir=self.output_dir,
                                      filename="ReactAdmin",
                                      context={
                                          "title":
                                          self.module_name,
                                          "resources":
                                          self._resources,
                                          "permissions":
                                          self.permissions,
                                          "permissions_store":
                                          self.permissions_store,
                                          "supported_components":
                                          SUPPORTED_COMPONENTS.values()
                                      },
                                      source="ReactAdmin_permissions"
                                      if self.permissions else "ReactAdmin")
        click.secho("Generating menu...", fg="blue")
        self.create_and_generate_file(_dir=self.output_dir,
                                      filename="Menu",
                                      context={"resources": self._resources})
        click.secho("Generation auth provider...", fg="blue")
        auth_dir = self.get_and_create_directory("auth")
        self.create_and_generate_file(_dir=auth_dir,
                                      filename="authProvider",
                                      context={
                                          "permissions":
                                          self.permissions,
                                          "permissions_store":
                                          self.permissions_store
                                      })
        click.secho("Generating data provider...", fg="blue")
        self.create_and_generate_file(_dir=self.output_dir,
                                      filename="dataProvider",
                                      context={"resources": self._resources})
        click.secho("Generating catch all...", fg="blue")
        self.create_and_generate_file(_dir=self.output_dir,
                                      filename="catchAll",
                                      context={
                                          "permissions":
                                          self.permissions,
                                          "permissions_store":
                                          self.permissions_store
                                      })
        click.secho("Generating utils file...", fg="blue")
        self.create_and_generate_file(_dir=self.output_dir,
                                      filename="utils",
                                      context={
                                          "permissions":
                                          self.permissions,
                                          "permissions_store":
                                          self.permissions_store
                                      })

        click.secho("Generating resource component files...", fg="blue")
        resource_dir = self.get_and_create_directory("resources")
        for name, resource in self._resources.items():
            title = resource.get("title", None)
            if title:
                self.create_and_generate_file(
                    _dir=resource_dir,
                    filename=title,
                    context={
                        "title": title,
                        "name": name,
                        "resource": resource,
                        "omit_exporter": self.omit_exporter,
                        "permissions": self.permissions,
                        "permissions_store": self.permissions_store,
                        "supported_components": SUPPORTED_COMPONENTS.values()
                    },
                    source="Resource_permissions"
                    if self.permissions else "Resource")

        click.secho("Generating custom edit toolbar component files...",
                    fg="blue")
        action_dir = self.get_and_create_directory("customActions")
        for name, resource in self._resources.items():
            title = resource.get("title", None)
            if title:
                if "edit" in resource["methods"]:
                    action_file = f"{title}EditToolbar"
                    self.create_and_generate_file(
                        _dir=action_dir,
                        filename=action_file,
                        context={
                            "name": name,
                            "resource": resource,
                            "permissions": self.permissions,
                            "permissions_store": self.permissions_store
                        },
                        source="EditToolbar_permissions"
                        if self.permissions else "EditToolbar")

        if self.omit_exporter:
            click.secho("Generating List Action files...", fg="blue")
            for name, resource in self._resources.items():
                title = resource.get("title", None)
                if title:
                    action_file = f"{title}ListActions"
                    self.create_and_generate_file(
                        _dir=action_dir,
                        filename=action_file,
                        context={
                            "name": name,
                            "resource": resource,
                            "permissions": self.permissions,
                            "permissions_store": self.permissions_store
                        },
                        source="ListActions_permissions"
                        if self.permissions else "ListActions")

        click.secho("Generating Filter files for resources...", fg="blue")
        filter_dir = self.get_and_create_directory("filters")
        for name, resource in self._resources.items():
            if resource.get("filters", None) is not None:
                title = resource.get("title", None)
                if title:
                    filter_file = f"{title}Filter"
                    self.create_and_generate_file(_dir=filter_dir,
                                                  filename=filter_file,
                                                  context={
                                                      "title":
                                                      title,
                                                      "filters":
                                                      resource["filters"]
                                                  },
                                                  source="Filters")

        if self.permissions:
            self.create_additional_files(PERMISSION_ADDITIONAL_FILES)
            if self.permissions_store:
                click.secho("Generating Permissions Store...", fg="blue")
                auth_dir = self.get_and_create_directory("auth")
                self.create_and_generate_file(
                    _dir=auth_dir,
                    filename="PermissionsStore",
                    context={
                        "resources": self._resources,
                        "supported_components": SUPPORTED_COMPONENTS.values()
                    })

        self.create_additional_files(ADDITIONAL_FILES)
class Generator(object):

    def __init__(self, output_dir, module_name=DEFAULT_MODULE, verbose=False,
                 rest_server_url=None, permissions=False):
        self.parser = None
        self.module_name = module_name
        self._resources = None
        self.verbose = verbose
        self.output_dir = output_dir
        self.rest_server_url = rest_server_url
        self.permissions = permissions

    def load_specification(self, specification_path, spec_format=None):
        # If the swagger spec format is not specified explicitly, we try to
        # derive it from the specification path
        if not spec_format:
            filename = os.path.basename(specification_path)
            extension = filename.rsplit(".", 1)[-1]
            if extension in YAML_EXTENSIONS:
                spec_format = SPEC_YAML
            elif extension in JSON_EXTENSIONS:
                spec_format = SPEC_JSON
            else:
                raise RuntimeError("Could not infer specification format. Use "
                                   "--spec-format to specify it explicitly.")

        click.secho("Using spec format '{}'".format(spec_format), fg="green")
        if spec_format == SPEC_YAML:
            with open(specification_path, "r") as f:
                self.parser = SwaggerParser(swagger_yaml=f)
        else:
            self.parser = SwaggerParser(swagger_path=specification_path)

        self._make_resource_definitions()

    def _get_definition_from_ref(self, definition):
        if "$ref" in definition:
            definition_name = \
                self.parser.get_definition_name_from_ref(definition["$ref"])
            ref_def = self.parser.specification["definitions"][definition_name]
            title = definition_name.replace("_", " ").title().replace(" ", "")
            return ref_def, title
        else:
            return definition, None

    def _get_resource_attributes(self, resource_name, properties,
                                 definition, suffix, fields=None):
        attributes = []
        found_reference = False
        for name, details in properties.items():
            # Check for desired fields and continue if not in there.
            if fields is not None:
                if name not in fields:
                    continue
            # Handle reference definition
            _property, title = self._get_definition_from_ref(details)
            if _property.get("properties", None) is not None:
                continue
            attribute = {
                "source": name,
                "type": _property.get("type", None),
                "required": name in definition.get("required", []),
                "read_only": _property.get("readOnly", False)
                if suffix == "Input" else False
            }
            # Add DisabledInput to Imports if read_only is true.
            if attribute["read_only"] and "DisabledInput" \
                    not in self._resources[resource_name]["imports"]:
                self._resources[resource_name]["imports"].append(
                    "DisabledInput")

            # Based on the type/format combination get the correct
            # AOR component to use.
            related_field = False
            if attribute["type"] in COMPONENT_MAPPING[suffix]:
                # Check if it is a related field or not
                if _property.get("x-related-info", None) is not None:
                    if self.permissions and not found_reference:
                        found_reference = True
                        custom_imports = [
                            custom["name"]
                            for custom in self._resources[resource_name]["custom_imports"]
                        ]
                        if "EmptyField" not in custom_imports:
                            self._resources[resource_name]["custom_imports"].append(
                                CUSTOM_IMPORTS["empty"]
                            )
                    related_info = _property["x-related-info"]
                    model = related_info.get("model", False)
                    # Check if related model is not set to None.
                    if model is not None:
                        related_field = True
                        # If model didn't even exist then attempt to guess the model
                        # from the substring before the last "_".
                        if not model:
                            model = name.rsplit("_", 1)[0]
                        attribute["label"] = model.replace("_", " ").title()
                        # If a custom base path has been given set the reference to it
                        # else attempt to get the plural of the given model.
                        if related_info.get("rest_resource_name", None) is not None:
                            reference = related_info["rest_resource_name"]
                        else:
                            reference = words.plural(model.replace("_", ""))
                        attribute["reference"] = reference
                        # Get the option text to be used in the Select input from the
                        # label field, else guess it from the current property name.
                        guess = name.rsplit("_", 1)[1]
                        label = related_info.get("label", None) or guess
                        attribute["option_text"] = label

                elif name.endswith("_id"):
                    related_field = True
                    relation = name.replace("_id", "")
                    attribute["label"] = relation.title()
                    attribute["reference"] = words.plural(relation)
                    attribute["related_field"] = "id"

                # LongTextFields don't exist
                # Handle component after figuring out if a related field or not.
                if not related_field:
                    if _property.get("format", None) in COMPONENT_MAPPING[suffix]:
                        # DateTimeField is currently not supported.
                        if suffix == "Field" and _property["format"] == "date-time":
                            _type = "date"
                        else:
                            _type = _property["format"]
                        attribute["component"] = \
                            COMPONENT_MAPPING[suffix][_type]
                    else:
                        attribute["component"] = \
                            COMPONENT_MAPPING[suffix][attribute["type"]]
                else:
                    attribute["component"] = \
                        COMPONENT_MAPPING[suffix]["relation"]
                    if suffix != "Input":
                        attribute["related_component"] = \
                            COMPONENT_MAPPING[suffix][attribute["type"]]
                    else:
                        attribute["related_component"] = "SelectInput"

            # Handle an enum possibility
            if _property.get("enum", None) is not None:
                attribute["component"] = COMPONENT_MAPPING[suffix]["enum"]
                # Only add choices if an input
                if suffix == "Input":
                    attribute["choices"] = _property["enum"]

            if attribute.get("component", None) is not None:
                # Add component to resource imports if not there.
                if attribute["component"] not in \
                        self._resources[resource_name]["imports"] and \
                        attribute["component"] not in CUSTOM_COMPONENTS:
                    self._resources[resource_name]["imports"].append(
                        attribute["component"]
                    )
                # Add related component to resource imports if not there.
                if attribute.get("related_component", None) is not None:
                    if attribute["related_component"] not in \
                            self._resources[resource_name]["imports"]:
                        self._resources[resource_name]["imports"].append(
                            attribute["related_component"]
                        )
                attributes.append(attribute)
            # Check for custom import types here.
            _type = "{}-{}".format(attribute["type"], suffix.lower())
            _format = "{}-{}".format(_property.get("format",
                                                   ""), suffix.lower())
            if _type in CUSTOM_IMPORTS or _format in CUSTOM_IMPORTS:
                custom_imports = [
                    custom["name"]
                    for custom in self._resources[resource_name]["custom_imports"]
                ]
                _import = CUSTOM_IMPORTS.get(
                    _format) or CUSTOM_IMPORTS.get(_type)
                if _import["name"] not in custom_imports:
                    self._resources[resource_name]["custom_imports"].append(
                        _import
                    )

        return attributes

    def _get_resource_from_definition(self, resource_name, head_component,
                                      definition, permissions=None):
        self._resources[resource_name][head_component] = {
            "permissions": permissions or []
        }
        suffix = COMPONENT_SUFFIX[head_component]
        properties = definition.get("properties", {})
        resource = self._get_resource_attributes(
            resource_name=resource_name,
            properties=properties,
            definition=definition,
            suffix=suffix
        )
        # Only add if there is something in resource
        if resource:
            self._resources[resource_name][head_component]["fields"] = resource

        # Check if there are inline models for the given resource.
        inlines = self.parser.specification.get(
            "x-detail-page-definitions", None
        )
        # Inlines are only shown on the Show and Edit components.
        if inlines is not None and head_component in ["show", "edit"]:
            if resource_name in inlines:
                if self.permissions:
                    custom_imports = [
                        custom["name"]
                        for custom in self._resources[resource_name]["custom_imports"]
                    ]
                    if "EmptyField" not in custom_imports:
                        self._resources[resource_name]["custom_imports"].append(
                            CUSTOM_IMPORTS["empty"]
                        )
                self._resources[resource_name][head_component]["inlines"] = []
                inlines = inlines[resource_name]["inlines"]
                for inline in inlines:
                    model = inline["model"]
                    label = inline.get("label", None)
                    # If a custom base path has been given.
                    if inline.get("rest_resource_name", None) is not None:
                        reference = inline["rest_resource_name"]
                    else:
                        reference = words.plural(model.replace("_", ""))
                    fields = inline.get("fields", None)
                    many_field = {
                        "label": label or model.replace("_", " ").title(),
                        "reference": reference,
                        "target": inline["key"],
                        "component": COMPONENT_MAPPING[suffix]["many"]
                    }
                    # Add ReferenceMany component to imports
                    if many_field["component"] not in \
                            self._resources[resource_name]["imports"]:
                        self._resources[resource_name]["imports"].append(
                            many_field["component"]
                        )
                    inline_def = \
                        self.parser.specification["definitions"][inline["model"]]
                    properties = inline_def.get("properties", {})
                    many_field["fields"] = self._get_resource_attributes(
                        resource_name=resource_name,
                        properties=properties,
                        definition=inline_def,
                        suffix="Field",
                        fields=fields
                    )
                    self._resources[resource_name][head_component]["inlines"].append(
                        many_field
                    )

    def _make_resource_definitions(self):
        self._resources = {}

        permission_imports_loaded = False
        for path, verbs in self.parser.specification["paths"].items():
            for verb, io in verbs.items():

                # Check if this is not a valid path method then skip it.
                if verb == "parameters":
                    continue
                else:
                    operation_id = io.get("operationId", "")
                    valid_operation = any([
                        operation in operation_id
                        for operation in OPERATION_SUFFIXES
                    ])
                    if operation_id and not valid_operation:
                        continue

                # Get resource name and path and add it to the list
                # for the first occurring instance of the resource

                name = operation_id.split("_")[0]
                if name not in self._resources:
                    permission_imports_loaded = False
                    self._resources[name] = {
                        "path": path[1:].split("/")[0],
                        "imports": [],
                        "custom_imports": [],
                        "has_methods": False,
                        "filter_lengths": {}
                    }

                definition = None
                head_component = None
                permissions = io.get("x-aor-permissions",
                                     []) if self.permissions else None

                if not permission_imports_loaded and self.permissions:
                    permission_imports_loaded = True
                    self._resources[name]["custom_imports"].append(
                        CUSTOM_IMPORTS["permissions"]
                    )

                # Get the correct definition/head_component/component suffix per
                # verb based on the operation.
                _create = "create" in operation_id
                _update = "update" in operation_id
                if "read" in operation_id:
                    definition, title = self._get_definition_from_ref(
                        definition=io["responses"]["200"]["schema"]
                    )
                    self._resources[name]["title"] = title or name
                    head_component = "show"
                    # Add show component imports
                    if "Show" not in self._resources[name]["imports"]:
                        self._resources[name]["imports"].append("Show")
                        self._resources[name]["imports"].append(
                            "SimpleShowLayout")
                elif "list" in operation_id:
                    definition, title = self._get_definition_from_ref(
                        definition=io["responses"]["200"]["schema"]["items"]
                    )
                    head_component = "list"
                    # Add list component imports
                    if "List" not in self._resources[name]["imports"]:
                        self._resources[name]["imports"].append("List")
                        self._resources[name]["imports"].append("Datagrid")
                    filters = []
                    filter_imports = []
                    # Get all method filters for the list component.
                    for parameter in io.get("parameters", []):
                        # If the parameter is a reference, get the actual parameter.
                        if "$ref" in parameter:
                            ref = parameter["$ref"].split("/")[2]
                            param = self.parser.specification["parameters"][ref]
                        else:
                            param = parameter
                        # Filters are only in the query string and their type needs
                        # to be a supported component.
                        if param["in"] == "query" \
                                and param["type"] in COMPONENT_MAPPING["Input"]\
                                and not param.get("x-admin-on-rest-exclude", False):
                            # Get component based on the explicit declaration or just the type.
                            declared_input = param.get("x-aor-filter", None)
                            related_input = param.get("x-related-info", None)
                            _type = param["type"]
                            relation = None
                            if declared_input:
                                _range = "-range" if declared_input.get(
                                    "range", False) else ""
                                _type = "{_type}{_range}".format(
                                    _type=declared_input["format"],
                                    _range=_range
                                )
                            elif related_input:
                                _type = "relation"
                                relation = {
                                    "component": COMPONENT_MAPPING["Input"]["enum"],
                                    "resource": related_input["rest_resource_name"],
                                    "text": related_input.get("label", None)
                                }
                                if relation["component"] not in filter_imports:
                                    filter_imports.append(relation["component"])
                            component = COMPONENT_MAPPING["Input"][_type]
                            # Add props if needed.
                            props = None
                            if _type in PROPS_MAPPING["Input"]:
                                props = PROPS_MAPPING["Input"][_type]
                            # Add component to filter imports if not there.
                            if component not in filter_imports:
                                filter_imports.append(component)
                            source = param["name"]
                            label = source.replace("_", " ").title()
                            _min = param.get("minLength", None)
                            _max = param.get("maxLength", None)
                            if _min or _max:
                                self._resources[name]["filter_lengths"][source] = {
                                    "min_length": _min,
                                    "max_length": _max
                                }
                            # Handle Array filter types finally!
                            array_validation = param["items"]["type"] \
                                if _type == "array" else None
                            filters.append({
                                "source": source,
                                "label": label,
                                "title": label.replace(" ", ""),
                                "component": component,
                                "relation": relation,
                                "props": props,
                                "array": array_validation
                            })
                    if filters:
                        self._resources[name]["filters"] = {
                            "filters": filters,
                            "imports": filter_imports
                        }
                elif _create or _update:
                    for parameter in io.get("parameters", []):
                        # If the parameter is a reference, get the actual parameter.
                        if "$ref" in parameter:
                            ref = parameter["$ref"].split("/")[2]
                            param = self.parser.specification["parameters"][ref]
                        else:
                            param = parameter
                        # Grab the body parameter as the create definition
                        if param["in"] == "body":
                            definition, title = self._get_definition_from_ref(
                                definition=param["schema"]
                            )
                    head_component = "create" if _create else "edit"
                    # Add SimpleForm and the head component to the imports
                    if "SimpleForm" not in self._resources[name]["imports"]:
                        self._resources[name]["imports"].append("SimpleForm")
                    the_import = head_component.title()
                    if the_import not in self._resources[name]["imports"]:
                        self._resources[name]["imports"].append(the_import)
                elif "delete" in operation_id:
                    self._resources[name]["remove"] = {
                        "permissions": permissions
                    }
                if head_component and definition:
                    # Toggle to be included in AOR if it has a single method.
                    self._resources[name]["has_methods"] = True
                    self._get_resource_from_definition(
                        resource_name=name,
                        head_component=head_component,
                        definition=definition,
                        permissions=permissions
                    )

    @staticmethod
    def generate_js_file(filename, context):
        """
        Generate a js file from the given specification.
        :param filename: The name of the template file.
        :param context: Context to be passed.
        :return: str
        """
        return render_to_string(filename, context)

    @staticmethod
    def add_additional_file(filename):
        """
        Add an additional file, that does not require context,
        to the generated admin.
        :return: str
        """
        return render_to_string(filename, {})

    def aor_generation(self):
        click.secho("Generating App.js component file...", fg="green")
        with open(os.path.join(self.output_dir, "App.js"), "w") as f:
            data = self.generate_js_file(
                filename="App.js",
                context={
                    "title": self.module_name,
                    "rest_server_url": self.rest_server_url,
                    "resources": self._resources,
                    "supported_components": SUPPORTED_COMPONENTS,
                    "add_permissions": self.permissions
                })
            f.write(data)
            if self.verbose:
                print(data)
        click.secho("Generating Menu.js component file...", fg="green")
        with open(os.path.join(self.output_dir, "Menu.js"), "w") as f:
            data = self.generate_js_file(
                filename="Menu.js",
                context={
                    "resources": self._resources
                })
            f.write(data)
            if self.verbose:
                print(data)
        click.secho("Generating resource component files...", fg="blue")
        resource_dir = self.output_dir + "/resources"
        if not os.path.exists(resource_dir):
            os.makedirs(resource_dir)
        for name, resource in self._resources.items():
            title = resource.get("title", None)
            if title:
                click.secho("Generating {}.js file...".format(
                    title), fg="green")
                with open(os.path.join(resource_dir, "{}.js".format(title)), "w") as f:
                    data = self.generate_js_file(
                        filename="Resource.js",
                        context={
                            "name": title,
                            "resource": resource,
                            "supported_components": SUPPORTED_COMPONENTS,
                            "add_permissions": self.permissions
                        }
                    )
                    f.write(data)
                    if self.verbose:
                        print(data)
        click.secho("Generating Filter files for resources...", fg="blue")
        filter_dir = self.output_dir + "/filters"
        if not os.path.exists(filter_dir):
            os.makedirs(filter_dir)
        for name, resource in self._resources.items():
            if resource.get("filters", None) is not None:
                title = resource.get("title", None)
                if title:
                    click.secho("Generating {}Filter.js file...".format(
                        title), fg="green")
                    with open(os.path.join(filter_dir, "{}Filter.js".format(title)), "w") as f:
                        data = self.generate_js_file(
                            filename="Filters.js",
                            context={
                                "title": title,
                                "filters": resource["filters"]
                            })
                        f.write(data)
                        if self.verbose:
                            print(data)
        click.secho("Adding basic swagger rest server file...", fg="cyan")
        with open(os.path.join(self.output_dir, "swaggerRestServer.js"), "w") as f:
            data = self.generate_js_file(
                filename="swaggerRestServer.js",
                context={
                    "resources": self._resources
                }
            )
            f.write(data)
            if self.verbose:
                print(data)
        if self.permissions:
            path_dir = self.output_dir + "/auth"
            if not os.path.exists(path_dir):
                os.makedirs(path_dir)
            with open(os.path.join(path_dir, "PermissionsStore.js"), "w") as f:
                data = self.generate_js_file(
                    filename="PermissionsStore.js",
                    context={
                        "resources": self._resources,
                        "supported_components": SUPPORTED_COMPONENTS
                    }
                )
                f.write(data)
                if self.verbose:
                    print(data)
        # Generate additional Files
        for _dir, files in ADDITIONAL_FILES.items():
            if _dir != "root":
                path_dir = "{}/{}".format(self.output_dir, _dir)
                if not os.path.exists(path_dir):
                    os.makedirs(path_dir)
            else:
                path_dir = self.output_dir
            for file in files:
                click.secho("Adding {} file...".format(file), fg="cyan")
                with open(os.path.join(path_dir, file), "w") as f:
                    data = self.add_additional_file(file)
                    f.write(data)
                    if self.verbose:
                        print(data)