Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] New project layout #1265

Merged
merged 24 commits into from
Jun 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ec5c2d0
Initial project layout update
AlexHill Apr 12, 2015
61f119d
Allow project template's local_settings.py into repo
AlexHill Apr 12, 2015
d967e70
Make updated project template work with local_settings.py.template
AlexHill Apr 13, 2015
a65acb9
Restore ".template" suffix in test script
AlexHill Apr 13, 2015
b02b1b8
Shorten a line for pep8
AlexHill Apr 13, 2015
b09c4fc
First settings changes
AlexHill Apr 13, 2015
c8c87f1
Handle updated project layout in fabfile
AlexHill Jun 5, 2015
f156c4c
Handle updated project layout in supervisor.conf
AlexHill Jun 5, 2015
0c60e99
Update settings.py for new project layout
AlexHill Jun 5, 2015
333bb71
Fix URLs during tests
AlexHill Jun 5, 2015
c77ce20
Stop server before dropping database
AlexHill Jun 5, 2015
0f372ac
Fix paths in settings
AlexHill Jun 5, 2015
066445a
More refinements to fabfile
AlexHill Jun 6, 2015
a15d86c
Give all deploy files the .template extension
AlexHill Jun 6, 2015
9b0d69b
Add utility function real_project_name
AlexHill Jun 10, 2015
f90e4f6
Use real_project_name in fabfile.py
AlexHill Jun 10, 2015
8ae404d
Use real_project_name in manage.py
AlexHill Jun 10, 2015
f65b5b9
Add missing blank line
AlexHill Jun 10, 2015
eafbf4b
Use real_project_name in wsgi.py
AlexHill Jun 10, 2015
b22bf08
Use unicode paths in project template handling
AlexHill Jun 10, 2015
e7929ed
Add missing trailing bracket
AlexHill Jun 10, 2015
541592b
Remove whitespace from blank line
AlexHill Jun 10, 2015
323ab16
Fix fix import
AlexHill Jun 10, 2015
a803307
Add BASE_DIR to settings
AlexHill Jun 10, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
69 changes: 69 additions & 0 deletions mezzanine/bin/management/commands/mezzanine_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from shutil import move
import django
from django.core.management import CommandError
from os import path

from django.core.management.commands.startproject import Command as BaseCommand
from django.utils import six
from django.utils.crypto import get_random_string
import os

import mezzanine


class Command(BaseCommand):
help = BaseCommand.help.replace("Django", "Mezzanine")

def handle(self, *args, **options):

# Overridden to provide a template value for nevercache_key. The
# method is copied verbatim from startproject.Command."""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
options['nevercache_key'] = get_random_string(50, chars)

# Indicate that local_settings.py.template should be rendered
options['files'] = ['local_settings.py.template']

super(Command, self).handle(*args, **options)

if django.VERSION < (1, 8):
proj_name, target = args + (None,) * max(0, 2 - len(args))
else:
proj_name, target = options.pop('name'), options.pop('directory')

project_dir = self.get_project_directory(proj_name, target)
project_app_dir = os.path.join(project_dir, proj_name)

# Now rename "local_settings.py.template" to "local_settings.py"
move(os.path.join(project_app_dir, "local_settings.py.template"),
os.path.join(project_app_dir, "local_settings.py"))

def get_project_directory(self, name, target):
"""This code is copied verbatim from Django's TemplateCommand.handle(),
but with the directory creation commented out."""

# if some directory is given, make sure it's nicely expanded
if target is None:
top_dir = path.join(os.getcwd(), name)
# try:
# os.makedirs(top_dir)
# except OSError as e:
# if e.errno == errno.EEXIST:
# message = "'%s' already exists" % top_dir
# else:
# message = e
# raise CommandError(message)
else:
top_dir = os.path.abspath(path.expanduser(target))
if not os.path.exists(top_dir):
raise CommandError("Destination directory '%s' does not "
"exist, please create it first." % top_dir)

return top_dir

def handle_template(self, template, subdir):
"""Use Mezzanine's project template by default. The method of picking
the default directory is copied from Django's TemplateCommand."""
if template is None:
return six.text_type(path.join(mezzanine.__path__[0], subdir))
return super(Command, self).handle_template(template, subdir)
85 changes: 8 additions & 77 deletions mezzanine/bin/mezzanine_project.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,18 @@
#!/usr/bin/env python
from __future__ import unicode_literals
from future.builtins import open
import sys

from distutils.dir_util import copy_tree
from optparse import OptionParser
import os
import re
from shutil import move
from uuid import uuid4

from mezzanine.utils.importing import path_for_import
from django.conf import settings
from django.core import management


def create_project():
"""
Copies the contents of the project_template directory to a new
directory specified as an argument to the command line.
"""

parser = OptionParser(usage="usage: %prog [options] project_name")
parser.add_option("-a", "--alternate", dest="alt", metavar="PACKAGE",
help="Alternate package to use, containing a project_template")
(options, args) = parser.parse_args()

if len(args) != 1:
parser.error("project_name must be specified")
project_name = args[0]
if not re.match("^[a-z0-9_]+$", project_name) or project_name[0].isdigit():
parser.error("%s is an invalid Python package name, specifically "
"it must start with a lowercase letter or underscore, followed "
"by zero or more lowercase letters, numbers, or underscores." %
project_name)
project_path = os.path.join(os.getcwd(), project_name)

# Ensure the given directory name doesn't clash with an existing
# Python package/module.
try:
__import__(project_name)
except ImportError:
pass
else:
parser.error("'%s' conflicts with the name of an existing "
"Python module and cannot be used as a project "
"name. Please try another name." % project_name)

# Create the list of packages to build from - at this stage it
# should only be one or two names, mezzanine plus an alternate
# package.
packages = ["mezzanine"]
if options.alt:
packages.append(options.alt)
for package_name in packages:
try:
__import__(package_name)
except ImportError:
parser.error("Could not import package '%s'" % package_name)

# Build the project up copying over the project_template from
# each of the packages. An alternate package will overwrite
# files from Mezzanine.
local_settings_path = os.path.join(project_path, "local_settings.py")
for package_name in packages:
package_path = path_for_import(package_name)
copy_tree(os.path.join(package_path, "project_template"), project_path)
move(local_settings_path + ".template", local_settings_path)
# Put mezzanine.conf in INSTALLED_APPS so call_command can find our command
settings.configure()
settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + ['mezzanine.bin']

# Generate a unique SECRET_KEY for the project's setttings module.
with open(local_settings_path, "r") as f:
data = f.read()
with open(local_settings_path, "w") as f:
make_key = lambda: "%s%s%s" % (uuid4(), uuid4(), uuid4())
data = data.replace("%(SECRET_KEY)s", make_key())
data = data.replace("%(NEVERCACHE_KEY)s", make_key())
f.write(data)
argv = sys.argv[:1] + ['mezzanine_project'] + sys.argv[1:]
management.execute_from_command_line(argv)

# Clean up pyc files.
for (root, dirs, files) in os.walk(project_path, False):
for f in files:
try:
if f.endswith(".pyc"):
os.remove(os.path.join(root, f))
except:
pass

if __name__ == "__main__":
create_project()
13 changes: 8 additions & 5 deletions mezzanine/bin/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,27 @@ def main(package="mezzanine"):
"""

from mezzanine.utils.importing import path_for_import

os.environ["DJANGO_SETTINGS_MODULE"] = "project_template.test_settings"
package_path = path_for_import(package)
project_path = os.path.join(package_path, "project_template")

local_settings_path = os.path.join(project_path, "local_settings.py")
test_settings_path = os.path.join(project_path, "test_settings.py")
os.environ["DJANGO_SETTINGS_MODULE"] = "project_name.test_settings"

project_app_path = os.path.join(project_path, "project_name")

local_settings_path = os.path.join(project_app_path, "local_settings.py")
test_settings_path = os.path.join(project_app_path, "test_settings.py")

sys.path.insert(0, package_path)
sys.path.insert(0, project_path)

if not os.path.exists(test_settings_path):
shutil.copy(local_settings_path + ".template", test_settings_path)
with open(test_settings_path, "r") as f:
local_settings = f.read()
with open(test_settings_path, "w") as f:
test_settings = """

from project_template import settings
from . import settings

globals().update(i for i in settings.__dict__.items() if i[0].isupper())

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[program:gunicorn_%(proj_name)s]
command=%(venv_path)s/bin/gunicorn -c gunicorn.conf.py -p gunicorn.pid wsgi:application
command=%(venv_path)s/bin/gunicorn -c gunicorn.conf.py -p gunicorn.pid %(proj_app)s.wsgi:application
directory=%(proj_path)s
user=%(user)s
autostart=true
Expand Down
32 changes: 19 additions & 13 deletions mezzanine/project_template/fabfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
import os
import re
import sys
from contextlib import contextmanager
from functools import wraps
from getpass import getpass, getuser
from glob import glob
from contextlib import contextmanager
from importlib import import_module
from posixpath import join

from mezzanine.utils.conf import real_project_name

from fabric.api import abort, env, cd, prefix, sudo as _sudo, run as _run, \
hide, task, local
from fabric.context_managers import settings as fab_settings
Expand All @@ -24,11 +27,13 @@
# Config setup #
################

env.proj_app = real_project_name("{{ project_name }}")

conf = {}
if sys.argv[0].split(os.sep)[-1] in ("fab", "fab-script.py"):
# Ensure we import settings from the current dir
try:
conf = __import__("settings", globals(), locals(), [], 0).FABRIC
conf = import_module("%s.settings" % env.proj_app).FABRIC
try:
conf["HOSTS"][0]
except (KeyError, ValueError):
Expand All @@ -44,7 +49,7 @@
env.key_filename = conf.get("SSH_KEY_PATH", None)
env.hosts = conf.get("HOSTS", [""])

env.proj_name = conf.get("PROJECT_NAME", os.getcwd().split(os.sep)[-1])
env.proj_name = conf.get("PROJECT_NAME", env.proj_app)
env.venv_home = conf.get("VIRTUALENV_HOME", "/home/%s/.virtualenvs" % env.user)
env.venv_path = join(env.venv_home, env.proj_name)
env.proj_path = "/home/%s/mezzanine/%s" % (env.user, env.proj_name)
Expand Down Expand Up @@ -81,17 +86,17 @@

templates = {
"nginx": {
"local_path": "deploy/nginx.conf",
"local_path": "deploy/nginx.conf.template",
"remote_path": "/etc/nginx/sites-enabled/%(proj_name)s.conf",
"reload_command": "service nginx restart",
},
"supervisor": {
"local_path": "deploy/supervisor.conf",
"local_path": "deploy/supervisor.conf.template",
"remote_path": "/etc/supervisor/conf.d/%(proj_name)s.conf",
"reload_command": "supervisorctl restart gunicorn_%(proj_name)s",
"reload_command": "supervisorctl update gunicorn_%(proj_name)s",
},
"cron": {
"local_path": "deploy/crontab",
"local_path": "deploy/crontab.template",
"remote_path": "/etc/cron.d/%(proj_name)s",
"owner": "root",
"mode": "600",
Expand All @@ -102,7 +107,7 @@
},
"settings": {
"local_path": "deploy/local_settings.py.template",
"remote_path": "%(proj_path)s/local_settings.py",
"remote_path": "%(proj_path)s/%(proj_app)s/local_settings.py",
},
}

Expand Down Expand Up @@ -364,9 +369,10 @@ def python(code, show=True):
"""
Runs Python code in the project's virtual environment, with Django loaded.
"""
setup = "import os; os.environ[\'DJANGO_SETTINGS_MODULE\']=\'settings\';" \
setup = "import os;" \
"os.environ[\'DJANGO_SETTINGS_MODULE\']=\'%s.settings\';" \
"import django;" \
"django.setup();"
"django.setup();" % env.proj_app
full_code = 'python -c "%s%s"' % (setup, code.replace("`", "\\\`"))
with project():
if show:
Expand Down Expand Up @@ -558,9 +564,9 @@ def remove():
sudo("rm %s" % remote_path)
if exists(env.repo_path):
run("rm -rf %s" % env.repo_path)
sudo("supervisorctl update")
psql("DROP DATABASE IF EXISTS %s;" % env.proj_name)
psql("DROP USER IF EXISTS %s;" % env.proj_name)
sudo("supervisorctl update")


##############
Expand Down Expand Up @@ -619,8 +625,6 @@ def deploy():
run("tar -cf {0}.tar {1} {0}".format(env.proj_name, exclude_arg))

# Deploy latest version of the project
for name in get_templates():
upload_template_and_reload(name)
with update_changed_requirements():
if env.deploy_tool in env.vcs_tools:
vcs_upload()
Expand All @@ -630,6 +634,8 @@ def deploy():
manage("collectstatic -v 0 --noinput")
manage("syncdb --noinput")
manage("migrate --noinput")
for name in get_templates():
upload_template_and_reload(name)
restart()
return True

Expand Down
30 changes: 5 additions & 25 deletions mezzanine/project_template/manage.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,14 @@
#!/usr/bin/env python
from __future__ import absolute_import, unicode_literals

import os
import sys

if __name__ == "__main__":

# Corrects some pathing issues in various contexts, such as cron jobs,
# and the project layout still being in Django 1.3 format.
from settings import PROJECT_ROOT, PROJECT_DIRNAME
sys.path.append(os.path.abspath(os.path.join(PROJECT_ROOT, "..")))


# Allow tests to be discovered during development.
if (PROJECT_DIRNAME == "project_template" and sys.argv[1] == "test" and
os.getcwd().split(os.sep)[-1] == PROJECT_DIRNAME):
os.chdir("..")


# Add the site ID CLI arg to the environment, which allows for the site
# used in any site related queries to be manually set for management
# commands.
for i, arg in enumerate(sys.argv):
if arg.startswith("--site"):
os.environ["MEZZANINE_SITE_ID"] = arg.split("=")[1]
sys.argv.pop(i)

from mezzanine.utils.conf import real_project_name

# Run Django.
if __name__ == "__main__":
settings_module = "%s.settings" % PROJECT_DIRNAME
settings_module = "%s.settings" % real_project_name("{{ project_name }}")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module)

from django.core.management import execute_from_command_line

execute_from_command_line(sys.argv)
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
DEBUG = True

# Make these unique, and don't share it with anybody.
SECRET_KEY = "%(SECRET_KEY)s"
NEVERCACHE_KEY = "%(NEVERCACHE_KEY)s"
SECRET_KEY = "{{ secret_key }}"
NEVERCACHE_KEY = "{{ nevercache_key }}"

DATABASES = {
"default": {
Expand Down
Loading