Skip to content

Commit

Permalink
feat: Add djangocms command to quickly start a project (#7702)
Browse files Browse the repository at this point in the history
  • Loading branch information
fsbraun committed Nov 27, 2023
1 parent 1867917 commit d031da3
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 62 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/test_startcmsproject.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: djangocms mysite test

on: [push, pull_request]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
create-project:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
django-version: [
'3.2', '4.2',
]
python-version: ['3.11']
requirements-file: ['requirements_base.txt']
os: [
ubuntu-20.04,
]

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}

uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Create project from template
run: |
sudo apt install gettext gcc -y
python -m venv .venv
source ./.venv/bin/activate
python -m pip install --upgrade pip
pip install -e .
djangocms mysite --noinput
9 changes: 9 additions & 0 deletions cms/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""
Invokes djangocms when the cms module is run as a script.
Example: python -m cms mysite
"""
from cms.management import djangocms

if __name__ == "__main__":
djangocms.execute_from_command_line()
177 changes: 177 additions & 0 deletions cms/management/commands/startcmsproject.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#!/usr/bin/env python
import os
import subprocess
import sys

from django.core.checks.security.base import SECRET_KEY_INSECURE_PREFIX
from django.core.management import CommandError
from django.core.management.templates import TemplateCommand
from django.core.management.utils import get_random_secret_key

from cms import __version__ as cms_version


class Command(TemplateCommand):
help = (
"Creates a django CMS project directory structure for the given project "
"name in the current directory or optionally in the given directory."
)
missing_args_message = "You must provide a project name."

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# Version major.minor
self.major_minor = ".".join(cms_version.split(".")[:2])

# Configure formatting
self.HEADING = lambda text: "\n" + self.style.SQL_FIELD(text)
self.COMMAND = self.style.HTTP_SUCCESS

def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument(
"--noinput",
"--no-input",
action="store_false",
dest="interactive",
help=(
"Tells django CMS to NOT prompt the user for input of any kind. "
"Superusers created with --noinput will "
"not be able to log in until they're given a valid password."
),
)
parser.add_argument(
"--username",
help="Specifies the login for the superuser to be created",
)
parser.add_argument("--email", help="Specifies the email for the superuser to be created")

def get_default_template(self):
return f"https://github.com/django-cms/cms-template/archive/{self.major_minor}.tar.gz"

def postprocess(self, project, options):
# Go to project dir
self.write_command(f'cd "{project}"')
os.chdir(project)

# Install requirements
self.install_requirements(project)

# Create database by running migrations
self.stdout.write(self.HEADING("Run migrations"))
self.run_management_command(["migrate"], capture_output=True)

# Create superuser (respecting command line arguments)
self.stdout.write(self.HEADING("Create superuser"))
command = ["createsuperuser"]
if options.get("username"):
command.append("--username")
command.append(options.get("username"))
if options.get("email"):
command.append("--email")
command.append(options.get("email"))
if not options["interactive"]:
if "--username" not in command:
command.append("--username")
command.append(os.environ.get("DJANGO_SUPERUSER_USERNAME", "admin"))
if "--email" not in command:
command.append("--email")
command.append(os.environ.get("DJANGO_SUPERUSER_EMAIL", "none@nowhere.com"))
command.append("--noinput")
self.run_management_command(command)

# Check installation
self.stdout.write(self.HEADING("Check installation"))
self.run_management_command(["cms", "check"], capture_output=True)

# Display success message
message = f"django CMS {cms_version} installed successfully"
separator = "*" * len(message)
self.stdout.write(self.HEADING(f"{separator}\n{message}\n{separator}"))
self.stdout.write(
f"""
Congratulations! You have successfully installed django CMS,
the lean enterprise content management powered by Django!
Now, to start the development server first go to your newly
created project and then call the runserver management command:
$ {self.style.SUCCESS("cd " + project)}
$ {self.style.SUCCESS("python -m manage runserver")}
Learn more at https://docs.django-cms.org/
Join the django CMS Slack channel http://www.django-cms.org/slack
Enjoy!
"""
)

def install_requirements(self, project):
for req_file in ("requirements.txt", "requirements.in"):
requirements = os.path.join(project, req_file)
if os.path.isfile(requirements):
if self.running_in_venv() or os.environ.get("DJANGOCMS_ALLOW_PIP_INSTALL", "False") == "True":
self.stdout.write(self.HEADING(f"Install requirements in {requirements}"))
self.write_command(f'python -m pip install -r "{requirements}"')
result = subprocess.run(
[sys.executable, "-m", "pip", "install", "-r", requirements],
capture_output=True,
)
if result.returncode:
self.stderr.write(self.style.ERROR(result.stderr.decode()))
raise CommandError(f"Failed to install requirements in {requirements}")
break
else:
self.stderr.write(
self.style.ERROR(
"To automatically install requirements for your new django CMS "
"project use this command in an virtual environment."
)
)
raise CommandError("Requirements not installed")

def run_management_command(self, commands, capture_output=False):
self.write_command("python -m manage " + " ".join(commands))
result = subprocess.run([sys.executable, "-m", "manage"] + commands, capture_output=capture_output)
if result.returncode:
if capture_output:
self.stderr.write(self.style.ERROR(result.stderr.decode()))
raise CommandError(f"{sys.executable} -m manage {' '.join(commands)} failed.")

def write_command(self, command):
self.stderr.write(self.COMMAND(command))

@staticmethod
def running_in_venv():
return sys.prefix != sys.base_prefix

def handle_template(self, template, subdir):
if not template:
template = self.get_default_template()
return super().handle_template(template, subdir)

def handle(self, **options):
# Capture target and name for postprocessing
name = options.pop("name")
directory = options.pop("directory", None)
# Create a random SECRET_KEY to put it in the main settings.
options["secret_key"] = SECRET_KEY_INSECURE_PREFIX + get_random_secret_key()

self.stdout.write(self.HEADING("Clone template using django-admin"))
command = (
f'django-admin startproject "{name}" ' f'--template {options["template"] or self.get_default_template()}'
)
if directory:
command += f' --directory "{directory}"'
self.write_command(command)

# Run startproject command
super().handle(
"project", name, directory, cms_version=cms_version, cms_docs_version=self.major_minor, **options
)

if directory is None:
top_dir = os.path.join(os.getcwd(), name)
else:
top_dir = os.path.abspath(os.path.expanduser(directory))
self.postprocess(top_dir, options)
24 changes: 24 additions & 0 deletions cms/management/djangocms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import os
import sys

from django.core.management import load_command_class


def execute_from_command_line(argv=None):
"""Run the startcmsproject management command."""

# Prepare arguments
argv = argv or sys.argv[:]
argv[0] = os.path.basename(argv[0])
if argv[0] == "__main__.py":
argv[0] = "python -m cms"

# Find command
command = load_command_class("cms", "startcmsproject")
if argv[1:] == ["--version"]:
from cms import __version__
sys.stdout.write(__version__ + "\n")
elif argv[1:] == ["--help"]:
command.print_help(argv[0], "")
else:
command.run_from_argv([argv[0], ""] + argv[1:]) # fake "empty" subcommand
65 changes: 65 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,3 +1,68 @@
[metadata]
name = django-cms
version = attr: cms.__version__
url = https://www.django-cms.org/
author = Django CMS Association and contributors
author_email = info@django-cms.org
description = Lean enterprise content management powered by Django.
long_description = file: README.rst
license = BSD-3-Clause
classifiers =
Development Status :: 5 - Production/Stable
Environment :: Web Environment
Framework :: Django
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
Framework :: Django
Framework :: Django :: 2.2
Framework :: Django :: 3.0
Framework :: Django :: 3.1
Framework :: Django :: 3.2
Framework :: Django :: 4.0
Framework :: Django :: 4.1
Framework :: Django :: 4.2
Framework :: Django :: 5.0
Topic :: Internet :: WWW/HTTP
Topic :: Internet :: WWW/HTTP :: Dynamic Content
Topic :: Software Development
Topic :: Software Development :: Libraries
Topic :: Software Development :: Libraries :: Application Frameworks
project_urls =
Documentation = https://docs.django-cms.org/
Release notes = https://docs.django-cms.org/en/latest/upgrade/index.html
Community = http://www.django-cms.org/slack
Source = https://github.com/django-cms/django-cms

[options]
python_requires = >=3.8
packages = find:
include_package_data = true
zip_safe = false
test_suite = runtests.main
install_requires =
Django >= 2.2
django-classy-tags >=0.7.2
django-formtools >=2.1
django-treebeard >=4.3
django-sekizai >=0.7
djangocms-admin-style >=1.2
packaging
setuptools
[options.entry_points]
console_scripts =
djangocms = cms.management.djangocms:execute_from_command_line

[options.extras_require]
argon2 = argon2-cffi >= 19.1.0
bcrypt = bcrypt

[bdist_wheel]
universal=1

Expand Down
64 changes: 2 additions & 62 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,5 @@
#!/usr/bin/env python
import os
from setuptools import setup

from cms import __version__
from setuptools import find_packages, setup


REQUIREMENTS = [
'Django>=2.2',
'django-classy-tags>=0.7.2',
'django-formtools>=2.1',
'django-treebeard>=4.3',
'django-sekizai>=0.7',
'djangocms-admin-style>=1.2',
'packaging',
'setuptools',
]


CLASSIFIERS = [
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Framework :: Django',
'Framework :: Django :: 2.2',
'Framework :: Django :: 3.0',
'Framework :: Django :: 3.1',
'Framework :: Django :: 3.2',
'Framework :: Django :: 4.0',
'Framework :: Django :: 4.1',
'Framework :: Django :: 4.2',
'Framework :: Django :: 5.0',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Application Frameworks',
]


setup(
name='django-cms',
version=__version__,
author='Django CMS Association and contributors',
author_email='info@django-cms.org',
url='https://www.django-cms.org/',
license='BSD-3-Clause',
description='Lean enterprise content management powered by Django.',
long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(),
packages=find_packages(exclude=['project', 'project.*']),
include_package_data=True,
zip_safe=False,
install_requires=REQUIREMENTS,
classifiers=CLASSIFIERS,
test_suite='runtests.main',
)
setup()
1 change: 1 addition & 0 deletions test_requirements/requirements_base.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-e .
argparse
coverage<5
dj-database-url
Expand Down

0 comments on commit d031da3

Please sign in to comment.