Source code for duck.cli.commands.collectstatic
"""
Module containing collectstatic command class.
"""
import os
import shutil
import pathlib
from typing import List, Generator, Tuple
from duck.routes import Blueprint
from duck.utils.path import joinpaths
from duck.logging import console
[docs]
class CollectStaticCommand:
# collectstatic command
[docs]
@classmethod
def setup(cls):
pass
[docs]
@classmethod
def main(cls, skip_confirmation: bool = False):
cls.setup()
cls.collectstatic(skip_confirmation)
[docs]
@classmethod
def collectstatic(cls, skip_confirmation: bool = False) -> None:
# Execute command after setup.
from duck.settings import SETTINGS
static_root = str(SETTINGS["STATIC_ROOT"])
blueprint_static_dirs: List[str, Blueprint] = list(cls.find_blueprint_static_dirs())
blueprint_staticfiles: List[str] = list(cls.get_blueprint_staticfiles(blueprint_static_dirs))
global_static_dirs = SETTINGS["GLOBAL_STATIC_DIRS"] or []
global_staticfiles = []
for static_dir in global_static_dirs:
if os.path.isdir(static_dir):
staticfiles = list(cls.recursive_getfiles(static_dir))
global_staticfiles.extend(staticfiles)
staticfiles_len = len(blueprint_staticfiles) + len(global_staticfiles)
if staticfiles_len == 0:
console.log_raw("\nNo staticfiles found!", level=console.WARNING)
return
if not skip_confirmation:
# Show confirmation prompt
console.log_raw(
f"\nWould you like to copy {staticfiles_len} staticfile(s) to {static_root} (y/N): ",
end="",
level=console.DEBUG,
)
# Obtain confirmation from console.
choice = input("")
if not choice.lower().startswith("y"):
console.log_raw("\nCancelled, bye!", level=console.WARNING)
return
for static_dir in global_static_dirs:
dst = static_root
if os.path.isdir(static_dir):
shutil.copytree(static_dir, dst, dirs_exist_ok=True)
for static_dir, blueprint in blueprint_static_dirs:
# Convert staticdir to relative path
abs_static_dir = static_dir
relative_static_dir = pathlib.Path(static_dir).relative_to(pathlib.Path(blueprint.location).parent)
# The blueprint stripped_staticdir is a dir with removed staticdir name from it
# so that function static() can resolve files correctly in production.
parts = pathlib.Path(relative_static_dir).parts
staticdir_name = ""
if parts:
staticdir_name = parts[0]
# Set staticdir without staticdir name
blueprint_stripped_staticdir = str(relative_static_dir).lstrip(staticdir_name)
dst = joinpaths(
static_root,
blueprint.name,
blueprint_stripped_staticdir,
)
# Copy static files
shutil.copytree(abs_static_dir, dst, dirs_exist_ok=True)
console.log_raw(f"\nSuccessfully copied {staticfiles_len} staticfile(s) to {static_root}", level=console.SUCCESS)
[docs]
@classmethod
def recursive_getfiles(cls, directory: str) -> Generator:
"""
Returns a generator for all files and subfiles within the directory.
"""
directory = pathlib.Path(directory)
if directory.is_dir():
for dir_entry in os.scandir(directory):
if dir_entry.is_file():
yield dir_entry.path
else:
for i in cls.recursive_getfiles(dir_entry.path):
yield i
[docs]
@classmethod
def find_blueprint_static_dirs(cls) -> Generator[Tuple[str, Blueprint], None, None]:
"""
Finds and returns static directories from all blueprint base directories.
Returns:
Generator: The generator of static directory and blueprint pair.
"""
from duck.settings import SETTINGS
from duck.settings.loaded import SettingsLoaded
from duck.etc.apps.defaultsite.blueprint import DuckSite
from duck.backend.django.setup import prepare_django, DjangoSetupWarning
# Setup Django if not set
# Try preparing Django backend
try:
prepare_django(True)
except Exception as e:
console.warn(f"Django setup failed: {e}", DjangoSetupWarning)
_blueprints = SettingsLoaded.BLUEPRINTS
# The DuckSite blueprint might not be available in BLUEPRINTS
# yet it might have some static files it would like to share with the whole app.
if DuckSite not in _blueprints:
_blueprints.append(DuckSite)
for blueprint in _blueprints:
if blueprint.enable_static_dir:
# Access to blueprint static files is allowed.
static_dir = joinpaths(
blueprint.root_directory,
blueprint.static_dir,
)
if pathlib.Path(static_dir).is_dir():
yield (static_dir, blueprint)
[docs]
@classmethod
def get_blueprint_staticfiles(cls, blueprint_static_dirs: Tuple[str, Blueprint]):
"""
Returns the generator to the found staticfiles.
Args:
blueprint_static_dirs (Tuple[str, Blueprint]): The collection of the static directory and the respective blueprint.
"""
for static_dir, blueprint in blueprint_static_dirs:
for static_file in cls.recursive_getfiles(static_dir):
yield static_file