mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-03 22:55:43 +00:00 
			
		
		
		
	Improve check for plugins file
This commit is contained in:
		@@ -1,7 +1,6 @@
 | 
				
			|||||||
"""Install a plugin into the python virtual environment."""
 | 
					"""Install a plugin into the python virtual environment."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import logging
 | 
					import logging
 | 
				
			||||||
import pathlib
 | 
					 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
import subprocess
 | 
					import subprocess
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
@@ -12,7 +11,6 @@ from django.utils.translation import gettext_lazy as _
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import plugin.models
 | 
					import plugin.models
 | 
				
			||||||
import plugin.staticfiles
 | 
					import plugin.staticfiles
 | 
				
			||||||
from InvenTree.config import get_plugin_dir
 | 
					 | 
				
			||||||
from InvenTree.exceptions import log_error
 | 
					from InvenTree.exceptions import log_error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
logger = logging.getLogger('inventree')
 | 
					logger = logging.getLogger('inventree')
 | 
				
			||||||
@@ -68,91 +66,41 @@ def handle_pip_error(error, path: str) -> list:
 | 
				
			|||||||
        raise ValidationError(errors[0])
 | 
					        raise ValidationError(errors[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def check_plugins_path(packagename: str) -> bool:
 | 
					def get_install_info(packagename: str) -> str:
 | 
				
			||||||
    """Determine if the package is installed in the plugins directory."""
 | 
					    """Determine the install information for a particular package.
 | 
				
			||||||
    # Remove version information
 | 
					 | 
				
			||||||
    for c in '<>=! ':
 | 
					 | 
				
			||||||
        packagename = packagename.split(c)[0]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    plugin_dir = get_plugin_dir()
 | 
					    - Uses 'pip show' to determine the install location of a package.
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not plugin_dir:
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    plugin_dir_path = pathlib.Path(plugin_dir)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not plugin_dir_path.exists():
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    result = pip_command('freeze', '--path', plugin_dir_path.absolute())
 | 
					 | 
				
			||||||
    output = result.decode('utf-8').split('\n')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Check if the package is installed in the plugins directory
 | 
					 | 
				
			||||||
    return any(re.match(rf'^{packagename}[\s=@]', line.strip()) for line in output)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def check_package_path(packagename: str) -> str:
 | 
					 | 
				
			||||||
    """Determine the install path of a particular package.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    - If installed, return the installation path
 | 
					 | 
				
			||||||
    - If not installed, return an empty string
 | 
					 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    logger.debug('check_package_path: %s', packagename)
 | 
					    logger.debug('get_install_info: %s', packagename)
 | 
				
			||||||
 | 
					 | 
				
			||||||
    # First check if the package is installed in the plugins directory
 | 
					 | 
				
			||||||
    if check_plugins_path(packagename):
 | 
					 | 
				
			||||||
        return f'plugins/{packagename}'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Remove version information
 | 
					    # Remove version information
 | 
				
			||||||
    for c in '<>=! ':
 | 
					    for c in '<>=!@ ':
 | 
				
			||||||
        packagename = packagename.split(c)[0]
 | 
					        packagename = packagename.split(c)[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    info = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        result = pip_command('show', packagename)
 | 
					        result = pip_command('show', packagename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        output = result.decode('utf-8').split('\n')
 | 
					        output = result.decode('utf-8').split('\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for line in output:
 | 
					        for line in output:
 | 
				
			||||||
            # Check if line matches pattern "Location: ..."
 | 
					            parts = line.split(':')
 | 
				
			||||||
            match = re.match(r'^Location:\s+(.+)$', line.strip())
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if match:
 | 
					            if len(parts) >= 2:
 | 
				
			||||||
                return match.group(1)
 | 
					                key = str(parts[0].strip().lower().replace('-', '_'))
 | 
				
			||||||
 | 
					                value = str(parts[1].strip())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                info[key] = value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except subprocess.CalledProcessError as error:
 | 
					    except subprocess.CalledProcessError as error:
 | 
				
			||||||
        log_error('check_package_path')
 | 
					        log_error('get_install_info')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        output = error.output.decode('utf-8')
 | 
					        output = error.output.decode('utf-8')
 | 
				
			||||||
 | 
					        info['error'] = output
 | 
				
			||||||
        logger.exception('Plugin lookup failed: %s', str(output))
 | 
					        logger.exception('Plugin lookup failed: %s', str(output))
 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # If we get here, the package is not installed
 | 
					    return info
 | 
				
			||||||
    return ''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def plugins_dir():
 | 
					 | 
				
			||||||
    """Return the path to the InvenTree custom plugins director.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Returns:
 | 
					 | 
				
			||||||
        pathlib.Path: Path to the custom plugins directory
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Raises:
 | 
					 | 
				
			||||||
        ValidationError: If the plugins directory is not specified, or does not exist
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    pd = get_plugin_dir()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not pd:
 | 
					 | 
				
			||||||
        raise ValidationError(_('Plugins directory not specified'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pd = pathlib.Path(pd)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not pd.exists():
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            pd.mkdir(parents=True, exist_ok=True)
 | 
					 | 
				
			||||||
        except Exception:
 | 
					 | 
				
			||||||
            raise ValidationError(_('Failed to create plugin directory'))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return pd.absolute()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def install_plugins_file():
 | 
					def install_plugins_file():
 | 
				
			||||||
@@ -165,9 +113,7 @@ def install_plugins_file():
 | 
				
			|||||||
        logger.warning('Plugin file %s does not exist', str(pf))
 | 
					        logger.warning('Plugin file %s does not exist', str(pf))
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    plugin_dir = plugins_dir()
 | 
					    cmd = ['install', '-U', '-r', str(pf)]
 | 
				
			||||||
 | 
					 | 
				
			||||||
    cmd = ['install', '-U', '--target', str(plugin_dir), '-r', str(pf)]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        pip_command(*cmd)
 | 
					        pip_command(*cmd)
 | 
				
			||||||
@@ -189,10 +135,19 @@ def install_plugins_file():
 | 
				
			|||||||
    return True
 | 
					    return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def update_plugins_file(install_name, remove=False):
 | 
					def update_plugins_file(install_name, full_package=None, version=None, remove=False):
 | 
				
			||||||
    """Add a plugin to the plugins file."""
 | 
					    """Add a plugin to the plugins file."""
 | 
				
			||||||
 | 
					    if remove:
 | 
				
			||||||
 | 
					        logger.info('Removing plugin from plugins file: %s', install_name)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
        logger.info('Adding plugin to plugins file: %s', install_name)
 | 
					        logger.info('Adding plugin to plugins file: %s', install_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # If a full package name is provided, use that instead
 | 
				
			||||||
 | 
					    if full_package and full_package != install_name:
 | 
				
			||||||
 | 
					        new_value = full_package
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        new_value = f'{install_name}=={version}' if version else install_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pf = settings.PLUGIN_FILE
 | 
					    pf = settings.PLUGIN_FILE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not pf or not pf.exists():
 | 
					    if not pf or not pf.exists():
 | 
				
			||||||
@@ -201,7 +156,7 @@ def update_plugins_file(install_name, remove=False):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def compare_line(line: str):
 | 
					    def compare_line(line: str):
 | 
				
			||||||
        """Check if a line in the file matches the installname."""
 | 
					        """Check if a line in the file matches the installname."""
 | 
				
			||||||
        return line.strip().split('==')[0] == install_name.split('==')[0]
 | 
					        return re.match(rf'^{install_name}[\s=@]', line.strip())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # First, read in existing plugin file
 | 
					    # First, read in existing plugin file
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
@@ -227,13 +182,13 @@ def update_plugins_file(install_name, remove=False):
 | 
				
			|||||||
            found = True
 | 
					            found = True
 | 
				
			||||||
            if not remove:
 | 
					            if not remove:
 | 
				
			||||||
                # Replace line with new install name
 | 
					                # Replace line with new install name
 | 
				
			||||||
                output.append(install_name)
 | 
					                output.append(new_value)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            output.append(line)
 | 
					            output.append(line)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Append plugin to file
 | 
					    # Append plugin to file
 | 
				
			||||||
    if not found and not remove:
 | 
					    if not found and not remove:
 | 
				
			||||||
        output.append(install_name)
 | 
					        output.append(new_value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Write file back to disk
 | 
					    # Write file back to disk
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
@@ -264,10 +219,8 @@ def install_plugin(url=None, packagename=None, user=None, version=None):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    logger.info('install_plugin: %s, %s', url, packagename)
 | 
					    logger.info('install_plugin: %s, %s', url, packagename)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    plugin_dir = plugins_dir()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # build up the command
 | 
					    # build up the command
 | 
				
			||||||
    install_name = ['install', '-U', '--target', str(plugin_dir)]
 | 
					    install_name = ['install', '-U']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    full_pkg = ''
 | 
					    full_pkg = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -302,15 +255,17 @@ def install_plugin(url=None, packagename=None, user=None, version=None):
 | 
				
			|||||||
        ret['result'] = ret['success'] = _('Installed plugin successfully')
 | 
					        ret['result'] = ret['success'] = _('Installed plugin successfully')
 | 
				
			||||||
        ret['output'] = str(result, 'utf-8')
 | 
					        ret['output'] = str(result, 'utf-8')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if packagename and (path := check_package_path(packagename)):
 | 
					        if packagename and (info := get_install_info(packagename)):
 | 
				
			||||||
            # Override result information
 | 
					            if path := info.get('location'):
 | 
				
			||||||
                ret['result'] = _(f'Installed plugin into {path}')
 | 
					                ret['result'] = _(f'Installed plugin into {path}')
 | 
				
			||||||
 | 
					                ret['version'] = info.get('version')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except subprocess.CalledProcessError as error:
 | 
					    except subprocess.CalledProcessError as error:
 | 
				
			||||||
        handle_pip_error(error, 'plugin_install')
 | 
					        handle_pip_error(error, 'plugin_install')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if version := ret.get('version'):
 | 
				
			||||||
        # Save plugin to plugins file
 | 
					        # Save plugin to plugins file
 | 
				
			||||||
    update_plugins_file(full_pkg)
 | 
					        update_plugins_file(packagename, full_package=full_pkg, version=version)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Reload the plugin registry, to discover the new plugin
 | 
					        # Reload the plugin registry, to discover the new plugin
 | 
				
			||||||
        from plugin.registry import registry
 | 
					        from plugin.registry import registry
 | 
				
			||||||
@@ -364,10 +319,12 @@ def uninstall_plugin(cfg: plugin.models.PluginConfig, user=None, delete_config=T
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    validate_package_plugin(cfg, user)
 | 
					    validate_package_plugin(cfg, user)
 | 
				
			||||||
    package_name = cfg.package_name
 | 
					    package_name = cfg.package_name
 | 
				
			||||||
    logger.info('Uninstalling plugin: %s', package_name)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if check_package_path(package_name):
 | 
					    pkg_info = get_install_info(package_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if path := pkg_info.get('location'):
 | 
				
			||||||
        # Uninstall the plugin using pip
 | 
					        # Uninstall the plugin using pip
 | 
				
			||||||
 | 
					        logger.info('Uninstalling plugin: %s from %s', package_name, path)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            pip_command('uninstall', '-y', package_name)
 | 
					            pip_command('uninstall', '-y', package_name)
 | 
				
			||||||
        except subprocess.CalledProcessError as error:
 | 
					        except subprocess.CalledProcessError as error:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user