From: Thomas Fillon Date: Wed, 19 Aug 2015 12:27:11 +0000 (+0200) Subject: Add Mezzanine/Cartridge + new models for records X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=3cbb32d09ca92c80386f014a7b5ee35e8a22c88c;p=diggersdigest.git Add Mezzanine/Cartridge + new models for records --- diff --git a/diggersdigest/__init__.py b/diggersdigest/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/diggersdigest/deploy/crontab.template b/diggersdigest/deploy/crontab.template new file mode 100644 index 0000000..c4af8cf --- /dev/null +++ b/diggersdigest/deploy/crontab.template @@ -0,0 +1,3 @@ +# Poll Twitter every 5 minutes +# Comment-out if you don't use Mezzanine's Twitter app +*/5 * * * * %(user)s %(manage)s poll_twitter diff --git a/diggersdigest/deploy/gunicorn.conf.py.template b/diggersdigest/deploy/gunicorn.conf.py.template new file mode 100644 index 0000000..257bb97 --- /dev/null +++ b/diggersdigest/deploy/gunicorn.conf.py.template @@ -0,0 +1,8 @@ +from __future__ import unicode_literals +import multiprocessing + +bind = "unix:%(proj_path)s/gunicorn.sock" +workers = %(num_workers)s +errorlog = "/home/%(user)s/logs/%(proj_name)s_error.log" +loglevel = "error" +proc_name = "%(proj_name)s" diff --git a/diggersdigest/deploy/local_settings.py.template b/diggersdigest/deploy/local_settings.py.template new file mode 100644 index 0000000..d8dc9a0 --- /dev/null +++ b/diggersdigest/deploy/local_settings.py.template @@ -0,0 +1,37 @@ +from __future__ import unicode_literals + +SECRET_KEY = "%(secret_key)s" +NEVERCACHE_KEY = "%(nevercache_key)s" +ALLOWED_HOSTS = [%(domains_python)s] + +DATABASES = { + "default": { + # Ends with "postgresql_psycopg2", "mysql", "sqlite3" or "oracle". + "ENGINE": "django.db.backends.postgresql_psycopg2", + # DB name or path to database file if using sqlite3. + "NAME": "%(proj_name)s", + # Not used with sqlite3. + "USER": "%(proj_name)s", + # Not used with sqlite3. + "PASSWORD": "%(db_pass)s", + # Set to empty string for localhost. Not used with sqlite3. + "HOST": "127.0.0.1", + # Set to empty string for default. Not used with sqlite3. + "PORT": "", + } +} + +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTOCOL", "https") + +CACHE_MIDDLEWARE_SECONDS = 60 + +CACHE_MIDDLEWARE_KEY_PREFIX = "%(proj_name)s" + +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.memcached.MemcachedCache", + "LOCATION": "127.0.0.1:11211", + } +} + +SESSION_ENGINE = "django.contrib.sessions.backends.cache" diff --git a/diggersdigest/deploy/nginx.conf.template b/diggersdigest/deploy/nginx.conf.template new file mode 100644 index 0000000..f99430a --- /dev/null +++ b/diggersdigest/deploy/nginx.conf.template @@ -0,0 +1,55 @@ + +upstream %(proj_name)s { + server unix:%(proj_path)s/gunicorn.sock fail_timeout=0; +} + +server { + + listen 80; + %(ssl_disabled)s listen 443 ssl; + server_name %(domains_nginx)s; + client_max_body_size 10M; + keepalive_timeout 15; + error_log /home/%(user)s/logs/%(proj_name)s_error_nginx.log info; + + %(ssl_disabled)s ssl_certificate conf/%(proj_name)s.crt; + %(ssl_disabled)s ssl_certificate_key conf/%(proj_name)s.key; + %(ssl_disabled)s ssl_session_cache shared:SSL:10m; + %(ssl_disabled)s ssl_session_timeout 10m; + %(ssl_disabled)s ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA; + %(ssl_disabled)s ssl_prefer_server_ciphers on; + + # Deny illegal Host headers + if ($host !~* ^(%(domains_regex)s)$) { + return 444; + } + + location / { + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Protocol $scheme; + proxy_pass http://%(proj_name)s; + } + + location /static/ { + root %(proj_path)s; + access_log off; + log_not_found off; + expires 30d; + } + + location /robots.txt { + root %(proj_path)s/static; + access_log off; + log_not_found off; + } + + location /favicon.ico { + root %(proj_path)s/static/img; + access_log off; + log_not_found off; + } + +} diff --git a/diggersdigest/deploy/supervisor.conf.template b/diggersdigest/deploy/supervisor.conf.template new file mode 100644 index 0000000..5f48e9b --- /dev/null +++ b/diggersdigest/deploy/supervisor.conf.template @@ -0,0 +1,9 @@ +[program:gunicorn_%(proj_name)s] +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 +stdout_logfile = /home/%(user)s/logs/%(proj_name)s_supervisor +autorestart=true +redirect_stderr=true +environment=LANG="%(locale)s",LC_ALL="%(locale)s",LC_LANG="%(locale)s" diff --git a/diggersdigest/diggersdigest/local_settings.py b/diggersdigest/diggersdigest/local_settings.py new file mode 100644 index 0000000..cff3f92 --- /dev/null +++ b/diggersdigest/diggersdigest/local_settings.py @@ -0,0 +1,51 @@ + +DEBUG = True + +# Make these unique, and don't share it with anybody. +SECRET_KEY = "+3b01&_6_m@@yb4f06$s0zno8vkybh81nbuj_q(xzk+xeih1+s" +NEVERCACHE_KEY = "l11tr%#!uc@+%$51(&+%=&z6h9yrw42(jpcj$3_&6evtu6hl%z" + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.mysql', + 'NAME': 'diggersdigest', + 'USER': 'digger', + 'PASSWORD': 'admin', + 'HOST': 'db', + 'PORT': 3306, + } +} + +# FileBrowser settings +# +# Max. Upload Size in Bytes: +# 10MB - 10485760 +# 20MB - 20971520 +# 50MB - 5242880 +# 100MB 104857600 +# 250MB - 214958080 +# 500MB - 429916160 +FILEBROWSER_MAX_UPLOAD_SIZE = 104857600 + +################### +# DEPLOY SETTINGS # +################### + +# Domains for public site +# ALLOWED_HOSTS = [""] + +# These settings are used by the default fabfile.py provided. +# Check fabfile.py for defaults. + +# FABRIC = { +# "DEPLOY_TOOL": "rsync", # Deploy with "git", "hg", or "rsync" +# "SSH_USER": "", # VPS SSH username +# "HOSTS": [""], # The IP address of your VPS +# "DOMAINS": ALLOWED_HOSTS, # Edit domains in ALLOWED_HOSTS +# "REQUIREMENTS_PATH": "requirements.txt", # Project's pip requirements +# "LOCALE": "en_US.UTF-8", # Should end with ".UTF-8" +# "DB_PASS": "", # Live database password +# "ADMIN_PASS": "", # Live admin user password +# "SECRET_KEY": SECRET_KEY, +# "NEVERCACHE_KEY": NEVERCACHE_KEY, +# } diff --git a/diggersdigest/diggersdigest/settings.py b/diggersdigest/diggersdigest/settings.py index abb96cd..4e9926e 100644 --- a/diggersdigest/diggersdigest/settings.py +++ b/diggersdigest/diggersdigest/settings.py @@ -1,107 +1,395 @@ -""" -Django settings for diggersdigest project. -Generated by 'django-admin startproject' using Django 1.8.3. +from __future__ import absolute_import, unicode_literals +import os +from django.utils.translation import ugettext_lazy as _ -For more information on this file, see -https://docs.djangoproject.com/en/1.8/topics/settings/ -For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.8/ref/settings/ -""" +###################### +# CARTRIDGE SETTINGS # +###################### -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -import os +# The following settings are already defined in cartridge.shop.defaults +# with default values, but are common enough to be put here, commented +# out, for conveniently overriding. Please consult the settings +# documentation for a full list of settings Cartridge implements: +# http://cartridge.jupo.org/configuration.html#default-settings + +# Sequence of available credit card types for payment. +# SHOP_CARD_TYPES = ("Mastercard", "Visa", "Diners", "Amex") + +# Setting to turn on featured images for shop categories. Defaults to False. +# SHOP_CATEGORY_USE_FEATURED_IMAGE = True + +# Set an alternative OrderForm class for the checkout process. +# SHOP_CHECKOUT_FORM_CLASS = 'cartridge.shop.forms.OrderForm' + +# If True, the checkout process is split into separate +# billing/shipping and payment steps. +# SHOP_CHECKOUT_STEPS_SPLIT = True + +# If True, the checkout process has a final confirmation step before +# completion. +# SHOP_CHECKOUT_STEPS_CONFIRMATION = True + +# Controls the formatting of monetary values accord to the locale +# module in the python standard library. If an empty string is +# used, will fall back to the system's locale. +#SHOP_CURRENCY_LOCALE = '' + +# Dotted package path and name of the function that +# is called on submit of the billing/shipping checkout step. This +# is where shipping calculation can be performed and set using the +# function ``cartridge.shop.utils.set_shipping``. +# SHOP_HANDLER_BILLING_SHIPPING = \ +# "cartridge.shop.checkout.default_billship_handler" + +# Dotted package path and name of the function that +# is called once an order is successful and all of the order +# object's data has been created. This is where any custom order +# processing should be implemented. +# SHOP_HANDLER_ORDER = "cartridge.shop.checkout.default_order_handler" + +# Dotted package path and name of the function that +# is called on submit of the payment checkout step. This is where +# integration with a payment gateway should be implemented. +# SHOP_HANDLER_PAYMENT = "cartridge.shop.checkout.default_payment_handler" + +# Sequence of value/name pairs for order statuses. +# SHOP_ORDER_STATUS_CHOICES = ( +# (1, "Unprocessed"), +# (2, "Processed"), +# ) + +# Sequence of value/name pairs for types of product options, +# eg Size, Colour. NOTE: Increasing the number of these will +# require database migrations! +# SHOP_OPTION_TYPE_CHOICES = ( +# (1, "Size"), +# (2, "Colour"), +# ) + +# Sequence of indexes from the SHOP_OPTION_TYPE_CHOICES setting that +# control how the options should be ordered in the admin, +# eg for "Colour" then "Size" given the above: +# SHOP_OPTION_ADMIN_ORDER = (2, 1) + + +###################### +# MEZZANINE SETTINGS # +###################### + +# The following settings are already defined with default values in +# the ``defaults.py`` module within each of Mezzanine's apps, but are +# common enough to be put here, commented out, for conveniently +# overriding. Please consult the settings documentation for a full list +# of settings Mezzanine implements: +# http://mezzanine.jupo.org/docs/configuration.html#default-settings -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +# Controls the ordering and grouping of the admin menu. +# +# ADMIN_MENU_ORDER = ( +# ("Content", ("pages.Page", "blog.BlogPost", +# "generic.ThreadedComment", (_("Media Library"), "fb_browse"),)), +# (_("Shop"), ("shop.Product", "shop.ProductOption", "shop.DiscountCode", +# "shop.Sale", "shop.Order")), +# ("Site", ("sites.Site", "redirects.Redirect", "conf.Setting")), +# ("Users", ("auth.User", "auth.Group",)), +# ) +# A three item sequence, each containing a sequence of template tags +# used to render the admin dashboard. +# +# DASHBOARD_TAGS = ( +# ("blog_tags.quick_blog", "mezzanine_tags.app_list"), +# ("comment_tags.recent_comments",), +# ("mezzanine_tags.recent_actions",), +# ) -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/ +# A sequence of templates used by the ``page_menu`` template tag. Each +# item in the sequence is a three item sequence, containing a unique ID +# for the template, a label for the template, and the template path. +# These templates are then available for selection when editing which +# menus a page should appear in. Note that if a menu template is used +# that doesn't appear in this setting, all pages will appear in it. -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'k#y*6^1y18xlm)y#i&v6!s26i1hgttz64&!--7ft3q-c!22y(%' +# PAGE_MENU_TEMPLATES = ( +# (1, _("Top navigation bar"), "pages/menus/dropdown.html"), +# (2, _("Left-hand tree"), "pages/menus/tree.html"), +# (3, _("Footer"), "pages/menus/footer.html"), +# ) -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +# A sequence of fields that will be injected into Mezzanine's (or any +# library's) models. Each item in the sequence is a four item sequence. +# The first two items are the dotted path to the model and its field +# name to be added, and the dotted path to the field class to use for +# the field. The third and fourth items are a sequence of positional +# args and a dictionary of keyword args, to use when creating the +# field instance. When specifying the field class, the path +# ``django.models.db.`` can be omitted for regular Django model fields. +# +# EXTRA_MODEL_FIELDS = ( +# ( +# # Dotted path to field. +# "mezzanine.blog.models.BlogPost.image", +# # Dotted path to field class. +# "somelib.fields.ImageField", +# # Positional args for field class. +# (_("Image"),), +# # Keyword args for field class. +# {"blank": True, "upload_to": "blog"}, +# ), +# # Example of adding a field to *all* of Mezzanine's content types: +# ( +# "mezzanine.pages.models.Page.another_field", +# "IntegerField", # 'django.db.models.' is implied if path is omitted. +# (_("Another name"),), +# {"blank": True, "default": 1}, +# ), +# ) +# Setting to turn on featured images for blog posts. Defaults to False. +# +# BLOG_USE_FEATURED_IMAGE = True + +# If True, the django-modeltranslation will be added to the +# INSTALLED_APPS setting. +USE_MODELTRANSLATION = False + + +######################## +# MAIN DJANGO SETTINGS # +######################## + +# Hosts/domain names that are valid for this site; required if DEBUG is False +# See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts ALLOWED_HOSTS = [] +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# On Unix systems, a value of None will cause Django to use the same +# timezone as the operating system. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'UTC' -# Application definition +# If you set this to True, Django will use timezone-aware datetimes. +USE_TZ = True -INSTALLED_APPS = ( - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'records', -) +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = "en" -MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'django.middleware.security.SecurityMiddleware', +# Supported languages +LANGUAGES = ( + ('en', _('English')), ) -ROOT_URLCONF = 'diggersdigest.urls' +# A boolean that turns on/off debug mode. When set to ``True``, stack traces +# are displayed for error pages. Should always be set to ``False`` in +# production. Best set to ``True`` in local_settings.py +DEBUG = False -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] +# Whether a user's session cookie expires when the Web browser is closed. +SESSION_EXPIRE_AT_BROWSER_CLOSE = True -WSGI_APPLICATION = 'diggersdigest.wsgi.application' +SITE_ID = 1 +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = False -# Database -# https://docs.djangoproject.com/en/1.8/ref/settings/#databases +AUTHENTICATION_BACKENDS = ("mezzanine.core.auth_backends.MezzanineBackend",) +# The numeric mode to set newly-uploaded files to. The value should be +# a mode you'd pass directly to os.chmod. +FILE_UPLOAD_PERMISSIONS = 0o644 + + + +############# +# DATABASES # +############# DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'diggersdigest', - 'USER': 'digger', - 'PASSWORD': 'admin', - 'HOST': 'db', - 'PORT': 3306, + "default": { + # Ends with "postgresql_psycopg2", "mysql", "sqlite3" or "oracle". + "ENGINE": "django.db.backends.sqlite3", + # DB name or path to database file if using sqlite3. + "NAME": "dev.db", + # Not used with sqlite3. + "USER": "", + # Not used with sqlite3. + "PASSWORD": "", + # Set to empty string for localhost. Not used with sqlite3. + "HOST": "", + # Set to empty string for default. Not used with sqlite3. + "PORT": "", } } -# Internationalization -# https://docs.djangoproject.com/en/1.8/topics/i18n/ +######### +# PATHS # +######### -LANGUAGE_CODE = 'en-us' +# Full filesystem path to the project. +PROJECT_APP_PATH = os.path.dirname(os.path.abspath(__file__)) +PROJECT_APP = os.path.basename(PROJECT_APP_PATH) +PROJECT_ROOT = BASE_DIR = os.path.dirname(PROJECT_APP_PATH) -TIME_ZONE = 'UTC' +# Every cache key will get prefixed with this value - here we set it to +# the name of the directory the project is in to try and use something +# project specific. +CACHE_MIDDLEWARE_KEY_PREFIX = PROJECT_APP -USE_I18N = True +# URL prefix for static files. +# Example: "http://media.lawrence.com/static/" +STATIC_URL = "/static/" -USE_L10N = True +# Absolute path to the directory static files should be collected to. +# Don't put anything in this directory yourself; store your static files +# in apps' "static/" subdirectories and in STATICFILES_DIRS. +# Example: "/home/media/media.lawrence.com/static/" +STATIC_ROOT = os.path.join(PROJECT_ROOT, STATIC_URL.strip("/")) -USE_TZ = True +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash. +# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" +MEDIA_URL = STATIC_URL + "media/" + +# Absolute filesystem path to the directory that will hold user-uploaded files. +# Example: "/home/media/media.lawrence.com/media/" +MEDIA_ROOT = os.path.join(PROJECT_ROOT, *MEDIA_URL.strip("/").split("/")) + +# Package/module name to import the root urlpatterns from for the project. +ROOT_URLCONF = "%s.urls" % PROJECT_APP + +# Put strings here, like "/home/html/django_templates" +# or "C:/www/django/templates". +# Always use forward slashes, even on Windows. +# Don't forget to use absolute paths, not relative paths. +TEMPLATE_DIRS = (os.path.join(PROJECT_ROOT, "templates"),) + + +################ +# APPLICATIONS # +################ + +INSTALLED_APPS = ( + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.redirects", + "django.contrib.sessions", + "django.contrib.sites", + "django.contrib.sitemaps", + "django.contrib.staticfiles", + "mezzanine.boot", + "mezzanine.conf", + "mezzanine.core", + "mezzanine.generic", + "mezzanine.pages", + "cartridge.shop", + "mezzanine.blog", + "mezzanine.forms", + "mezzanine.galleries", + "mezzanine.twitter", + # "mezzanine.accounts", + # "mezzanine.mobile", + "records", + +) + +# List of processors used by RequestContext to populate the context. +# Each one should be a callable that takes the request object as its +# only parameter and returns a dictionary to add to the context. +TEMPLATE_CONTEXT_PROCESSORS = ( + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.static", + "django.core.context_processors.media", + "django.core.context_processors.request", + "django.core.context_processors.tz", + "mezzanine.conf.context_processors.settings", + "mezzanine.pages.context_processors.page", +) + +# List of middleware classes to use. Order is important; in the request phase, +# these middleware classes will be applied in the order given, and in the +# response phase the middleware will be applied in reverse order. +MIDDLEWARE_CLASSES = ( + "mezzanine.core.middleware.UpdateCacheMiddleware", + + 'django.contrib.sessions.middleware.SessionMiddleware', + # Uncomment if using internationalisation or localisation + # 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + + "cartridge.shop.middleware.ShopMiddleware", + "mezzanine.core.request.CurrentRequestMiddleware", + "mezzanine.core.middleware.RedirectFallbackMiddleware", + "mezzanine.core.middleware.TemplateForDeviceMiddleware", + "mezzanine.core.middleware.TemplateForHostMiddleware", + "mezzanine.core.middleware.AdminLoginInterfaceSelectorMiddleware", + "mezzanine.core.middleware.SitePermissionMiddleware", + # Uncomment the following if using any of the SSL settings: + # "mezzanine.core.middleware.SSLRedirectMiddleware", + "mezzanine.pages.middleware.PageMiddleware", + "mezzanine.core.middleware.FetchFromCacheMiddleware", +) + +# Store these package names here as they may change in the future since +# at the moment we are using custom forks of them. +PACKAGE_NAME_FILEBROWSER = "filebrowser_safe" +PACKAGE_NAME_GRAPPELLI = "grappelli_safe" + +######################### +# OPTIONAL APPLICATIONS # +######################### + +# These will be added to ``INSTALLED_APPS``, only if available. +OPTIONAL_APPS = ( + "debug_toolbar", + "django_extensions", + "compressor", + PACKAGE_NAME_FILEBROWSER, + PACKAGE_NAME_GRAPPELLI, +) + +################## +# LOCAL SETTINGS # +################## + +# Allow any settings to be defined in local_settings.py which should be +# ignored in your version control system allowing for settings to be +# defined per machine. +try: + from .local_settings import * +except ImportError as e: + if "local_settings" not in str(e): + raise e -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.8/howto/static-files/ +#################### +# DYNAMIC SETTINGS # +#################### -STATIC_URL = '/static/' +# set_dynamic_settings() will rewrite globals based on what has been +# defined so far, in order to provide some better defaults where +# applicable. We also allow this settings module to be imported +# without Mezzanine installed, as the case may be when using the +# fabfile, where setting the dynamic settings below isn't strictly +# required. +try: + from mezzanine.utils.conf import set_dynamic_settings +except ImportError: + pass +else: + set_dynamic_settings(globals()) diff --git a/diggersdigest/diggersdigest/urls.py b/diggersdigest/diggersdigest/urls.py index 17ca863..7d5ee68 100644 --- a/diggersdigest/diggersdigest/urls.py +++ b/diggersdigest/diggersdigest/urls.py @@ -1,21 +1,109 @@ -"""diggersdigest URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.8/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Add an import: from blog import urls as blog_urls - 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) -""" -from django.conf.urls import include, url +from __future__ import unicode_literals + +from django.conf.urls import patterns, include, url +from django.conf.urls.i18n import i18n_patterns from django.contrib import admin -urlpatterns = [ - url(r'^admin/', include(admin.site.urls)), -] +from mezzanine.core.views import direct_to_template +from mezzanine.conf import settings + + +admin.autodiscover() + +# Add the urlpatterns for any custom Django applications here. +# You can also change the ``home`` view to add your own functionality +# to the project's homepage. + +urlpatterns = i18n_patterns("", + # Change the admin prefix here to use an alternate URL for the + # admin interface, which would be marginally more secure. + ("^admin/", include(admin.site.urls)), +) + +if settings.USE_MODELTRANSLATION: + urlpatterns += patterns('', + url('^i18n/$', 'django.views.i18n.set_language', name='set_language'), + ) + +urlpatterns += patterns('', + + # Cartridge URLs. + ("^shop/", include("cartridge.shop.urls")), + url("^account/orders/$", "cartridge.shop.views.order_history", + name="shop_order_history"), + + # We don't want to presume how your homepage works, so here are a + # few patterns you can use to set it up. + + # HOMEPAGE AS STATIC TEMPLATE + # --------------------------- + # This pattern simply loads the index.html template. It isn't + # commented out like the others, so it's the default. You only need + # one homepage pattern, so if you use a different one, comment this + # one out. + + url("^$", direct_to_template, {"template": "index.html"}, name="home"), + + # HOMEPAGE AS AN EDITABLE PAGE IN THE PAGE TREE + # --------------------------------------------- + # This pattern gives us a normal ``Page`` object, so that your + # homepage can be managed via the page tree in the admin. If you + # use this pattern, you'll need to create a page in the page tree, + # and specify its URL (in the Meta Data section) as "/", which + # is the value used below in the ``{"slug": "/"}`` part. Make + # sure to uncheck all templates for the "show in menus" field + # when you create the page, since the link to the homepage is + # always hard-coded into all the page menus that display navigation + # on the site. Also note that the normal rule of adding a custom + # template per page with the template name using the page's slug + # doesn't apply here, since we can't have a template called + # "/.html" - so for this case, the template "pages/index.html" can + # be used. + + # url("^$", "mezzanine.pages.views.page", {"slug": "/"}, name="home"), + + # HOMEPAGE FOR A BLOG-ONLY SITE + # ----------------------------- + # This pattern points the homepage to the blog post listing page, + # and is useful for sites that are primarily blogs. If you use this + # pattern, you'll also need to set BLOG_SLUG = "" in your + # ``settings.py`` module, and delete the blog page object from the + # page tree in the admin if it was installed. + + # url("^$", "mezzanine.blog.views.blog_post_list", name="home"), + + # MEZZANINE'S URLS + # ---------------- + # ADD YOUR OWN URLPATTERNS *ABOVE* THE LINE BELOW. + # ``mezzanine.urls`` INCLUDES A *CATCH ALL* PATTERN + # FOR PAGES, SO URLPATTERNS ADDED BELOW ``mezzanine.urls`` + # WILL NEVER BE MATCHED! + + # If you'd like more granular control over the patterns in + # ``mezzanine.urls``, go right ahead and take the parts you want + # from it, and use them directly below instead of using + # ``mezzanine.urls``. + ("^", include("mezzanine.urls")), + + # MOUNTING MEZZANINE UNDER A PREFIX + # --------------------------------- + # You can also mount all of Mezzanine's urlpatterns under a + # URL prefix if desired. When doing this, you need to define the + # ``SITE_PREFIX`` setting, which will contain the prefix. Eg: + # SITE_PREFIX = "my/site/prefix" + # For convenience, and to avoid repeating the prefix, use the + # commented out pattern below (commenting out the one above of course) + # which will make use of the ``SITE_PREFIX`` setting. Make sure to + # add the import ``from django.conf import settings`` to the top + # of this file as well. + # Note that for any of the various homepage patterns above, you'll + # need to use the ``SITE_PREFIX`` setting as well. + + # ("^%s/" % settings.SITE_PREFIX, include("mezzanine.urls")) + +) + +# Adds ``STATIC_URL`` to the context of error pages, so that error +# pages can use JS, CSS and images. +handler404 = "mezzanine.core.views.page_not_found" +handler500 = "mezzanine.core.views.server_error" diff --git a/diggersdigest/diggersdigest/wsgi.py b/diggersdigest/diggersdigest/wsgi.py index 1aed28a..87399eb 100644 --- a/diggersdigest/diggersdigest/wsgi.py +++ b/diggersdigest/diggersdigest/wsgi.py @@ -10,7 +10,9 @@ https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/ import os from django.core.wsgi import get_wsgi_application +from mezzanine.utils.conf import real_project_name -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diggersdigest.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", + "%s.settings" % real_project_name("diggersdigest")) application = get_wsgi_application() diff --git a/diggersdigest/fabfile.py b/diggersdigest/fabfile.py new file mode 100644 index 0000000..7559520 --- /dev/null +++ b/diggersdigest/fabfile.py @@ -0,0 +1,685 @@ +from __future__ import print_function, unicode_literals +from future.builtins import open + +import os +import re +import sys +from contextlib import contextmanager +from functools import wraps +from getpass import getpass, getuser +from glob import glob +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 +from fabric.contrib.console import confirm +from fabric.contrib.files import exists, upload_template +from fabric.contrib.project import rsync_project +from fabric.colors import yellow, green, blue, red +from fabric.decorators import hosts + + +################ +# Config setup # +################ + +env.proj_app = real_project_name("diggersdigest") + +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_module("%s.settings" % env.proj_app).FABRIC + try: + conf["HOSTS"][0] + except (KeyError, ValueError): + raise ImportError + except (ImportError, AttributeError): + print("Aborting, no hosts defined.") + exit() + +env.db_pass = conf.get("DB_PASS", None) +env.admin_pass = conf.get("ADMIN_PASS", None) +env.user = conf.get("SSH_USER", getuser()) +env.password = conf.get("SSH_PASS", None) +env.key_filename = conf.get("SSH_KEY_PATH", None) +env.hosts = conf.get("HOSTS", [""]) + +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) +env.manage = "%s/bin/python %s/manage.py" % (env.venv_path, env.proj_path) +env.domains = conf.get("DOMAINS", [conf.get("LIVE_HOSTNAME", env.hosts[0])]) +env.domains_nginx = " ".join(env.domains) +env.domains_regex = "|".join(env.domains) +env.domains_python = ", ".join(["'%s'" % s for s in env.domains]) +env.ssl_disabled = "#" if len(env.domains) > 1 else "" +env.vcs_tools = ["git", "hg"] +env.deploy_tool = conf.get("DEPLOY_TOOL", "rsync") +env.reqs_path = conf.get("REQUIREMENTS_PATH", None) +env.locale = conf.get("LOCALE", "en_US.UTF-8") +env.num_workers = conf.get("NUM_WORKERS", + "multiprocessing.cpu_count() * 2 + 1") + +env.secret_key = conf.get("SECRET_KEY", "") +env.nevercache_key = conf.get("NEVERCACHE_KEY", "") + +# Remote git repos need to be "bare" and reside separated from the project +if env.deploy_tool == "git": + env.repo_path = "/home/%s/git/%s.git" % (env.user, env.proj_name) +else: + env.repo_path = env.proj_path + + +################## +# Template setup # +################## + +# Each template gets uploaded at deploy time, only if their +# contents has changed, in which case, the reload command is +# also run. + +templates = { + "nginx": { + "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.template", + "remote_path": "/etc/supervisor/conf.d/%(proj_name)s.conf", + "reload_command": "supervisorctl update gunicorn_%(proj_name)s", + }, + "cron": { + "local_path": "deploy/crontab.template", + "remote_path": "/etc/cron.d/%(proj_name)s", + "owner": "root", + "mode": "600", + }, + "gunicorn": { + "local_path": "deploy/gunicorn.conf.py.template", + "remote_path": "%(proj_path)s/gunicorn.conf.py", + }, + "settings": { + "local_path": "deploy/local_settings.py.template", + "remote_path": "%(proj_path)s/%(proj_app)s/local_settings.py", + }, +} + + +###################################### +# Context for virtualenv and project # +###################################### + +@contextmanager +def virtualenv(): + """ + Runs commands within the project's virtualenv. + """ + with cd(env.venv_path): + with prefix("source %s/bin/activate" % env.venv_path): + yield + + +@contextmanager +def project(): + """ + Runs commands within the project's directory. + """ + with virtualenv(): + with cd(env.proj_path): + yield + + +@contextmanager +def update_changed_requirements(): + """ + Checks for changes in the requirements file across an update, + and gets new requirements if changes have occurred. + """ + reqs_path = join(env.proj_path, env.reqs_path) + get_reqs = lambda: run("cat %s" % reqs_path, show=False) + old_reqs = get_reqs() if env.reqs_path else "" + yield + if old_reqs: + new_reqs = get_reqs() + if old_reqs == new_reqs: + # Unpinned requirements should always be checked. + for req in new_reqs.split("\n"): + if req.startswith("-e"): + if "@" not in req: + # Editable requirement without pinned commit. + break + elif req.strip() and not req.startswith("#"): + if not set(">=<") & set(req): + # PyPI requirement without version. + break + else: + # All requirements are pinned. + return + pip("-r %s/%s" % (env.proj_path, env.reqs_path)) + + +########################################### +# Utils and wrappers for various commands # +########################################### + +def _print(output): + print() + print(output) + print() + + +def print_command(command): + _print(blue("$ ", bold=True) + + yellow(command, bold=True) + + red(" ->", bold=True)) + + +@task +def run(command, show=True, *args, **kwargs): + """ + Runs a shell comand on the remote server. + """ + if show: + print_command(command) + with hide("running"): + return _run(command, *args, **kwargs) + + +@task +def sudo(command, show=True, *args, **kwargs): + """ + Runs a command as sudo on the remote server. + """ + if show: + print_command(command) + with hide("running"): + return _sudo(command, *args, **kwargs) + + +def log_call(func): + @wraps(func) + def logged(*args, **kawrgs): + header = "-" * len(func.__name__) + _print(green("\n".join([header, func.__name__, header]), bold=True)) + return func(*args, **kawrgs) + return logged + + +def get_templates(): + """ + Returns each of the templates with env vars injected. + """ + injected = {} + for name, data in templates.items(): + injected[name] = dict([(k, v % env) for k, v in data.items()]) + return injected + + +def upload_template_and_reload(name): + """ + Uploads a template only if it has changed, and if so, reload the + related service. + """ + template = get_templates()[name] + local_path = template["local_path"] + if not os.path.exists(local_path): + project_root = os.path.dirname(os.path.abspath(__file__)) + local_path = os.path.join(project_root, local_path) + remote_path = template["remote_path"] + reload_command = template.get("reload_command") + owner = template.get("owner") + mode = template.get("mode") + remote_data = "" + if exists(remote_path): + with hide("stdout"): + remote_data = sudo("cat %s" % remote_path, show=False) + with open(local_path, "r") as f: + local_data = f.read() + # Escape all non-string-formatting-placeholder occurrences of '%': + local_data = re.sub(r"%(?!\(\w+\)s)", "%%", local_data) + if "%(db_pass)s" in local_data: + env.db_pass = db_pass() + local_data %= env + clean = lambda s: s.replace("\n", "").replace("\r", "").strip() + if clean(remote_data) == clean(local_data): + return + upload_template(local_path, remote_path, env, use_sudo=True, backup=False) + if owner: + sudo("chown %s %s" % (owner, remote_path)) + if mode: + sudo("chmod %s %s" % (mode, remote_path)) + if reload_command: + sudo(reload_command) + + +def rsync_upload(): + """ + Uploads the project with rsync excluding some files and folders. + """ + excludes = ["*.pyc", "*.pyo", "*.db", ".DS_Store", ".coverage", + "local_settings.py", "/static", "/.git", "/.hg"] + local_dir = os.getcwd() + os.sep + return rsync_project(remote_dir=env.proj_path, local_dir=local_dir, + exclude=excludes) + + +def vcs_upload(): + """ + Uploads the project with the selected VCS tool. + """ + if env.deploy_tool == "git": + remote_path = "ssh://%s@%s%s" % (env.user, env.host_string, + env.repo_path) + if not exists(env.repo_path): + run("mkdir -p %s" % env.repo_path) + with cd(env.repo_path): + run("git init --bare") + local("git push -f %s master" % remote_path) + with cd(env.repo_path): + run("GIT_WORK_TREE=%s git checkout -f master" % env.proj_path) + run("GIT_WORK_TREE=%s git reset --hard" % env.proj_path) + elif env.deploy_tool == "hg": + remote_path = "ssh://%s@%s/%s" % (env.user, env.host_string, + env.repo_path) + with cd(env.repo_path): + if not exists("%s/.hg" % env.repo_path): + run("hg init") + print(env.repo_path) + with fab_settings(warn_only=True): + push = local("hg push -f %s" % remote_path) + if push.return_code == 255: + abort() + run("hg update") + + +def db_pass(): + """ + Prompts for the database password if unknown. + """ + if not env.db_pass: + env.db_pass = getpass("Enter the database password: ") + return env.db_pass + + +@task +def apt(packages): + """ + Installs one or more system packages via apt. + """ + return sudo("apt-get install -y -q " + packages) + + +@task +def pip(packages): + """ + Installs one or more Python packages within the virtual environment. + """ + with virtualenv(): + return run("pip install %s" % packages) + + +def postgres(command): + """ + Runs the given command as the postgres user. + """ + show = not command.startswith("psql") + return sudo(command, show=show, user="postgres") + + +@task +def psql(sql, show=True): + """ + Runs SQL against the project's database. + """ + out = postgres('psql -c "%s"' % sql) + if show: + print_command(sql) + return out + + +@task +def backup(filename): + """ + Backs up the project database. + """ + tmp_file = "/tmp/%s" % filename + # We dump to /tmp because user "postgres" can't write to other user folders + # We cd to / because user "postgres" might not have read permissions + # elsewhere. + with cd("/"): + postgres("pg_dump -Fc %s > %s" % (env.proj_name, tmp_file)) + run("cp %s ." % tmp_file) + sudo("rm -f %s" % tmp_file) + + +@task +def restore(filename): + """ + Restores the project database from a previous backup. + """ + return postgres("pg_restore -c -d %s %s" % (env.proj_name, filename)) + + +@task +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\']=\'%s.settings\';" \ + "import django;" \ + "django.setup();" % env.proj_app + full_code = 'python -c "%s%s"' % (setup, code.replace("`", "\\\`")) + with project(): + if show: + print_command(code) + result = run(full_code, show=False) + return result + + +def static(): + """ + Returns the live STATIC_ROOT directory. + """ + return python("from django.conf import settings;" + "print(settings.STATIC_ROOT)", show=False).split("\n")[-1] + + +@task +def manage(command): + """ + Runs a Django management command. + """ + return run("%s %s" % (env.manage, command)) + + +########################### +# Security best practices # +########################### + +@task +@log_call +@hosts(["root@%s" % host for host in env.hosts]) +def secure(new_user=env.user): + """ + Minimal security steps for brand new servers. + Installs system updates, creates new user (with sudo privileges) for future + usage, and disables root login via SSH. + """ + run("apt-get update -q") + run("apt-get upgrade -y -q") + run("adduser --gecos '' %s" % new_user) + run("usermod -G sudo %s" % new_user) + run("sed -i 's:RootLogin yes:RootLogin no:' /etc/ssh/sshd_config") + run("service ssh restart") + print(green("Security steps completed. Log in to the server as '%s' from " + "now on." % new_user, bold=True)) + + +######################### +# Install and configure # +######################### + +@task +@log_call +def install(): + """ + Installs the base system and Python requirements for the entire server. + """ + # Install system requirements + sudo("apt-get update -y -q") + apt("nginx libjpeg-dev python-dev python-setuptools git-core " + "postgresql libpq-dev memcached supervisor python-pip") + run("mkdir -p /home/%s/logs" % env.user) + + # Install Python requirements + sudo("pip install -U pip virtualenv virtualenvwrapper mercurial") + + # Set up virtualenv + run("mkdir -p %s" % env.venv_home) + run("echo 'export WORKON_HOME=%s' >> /home/%s/.bashrc" % (env.venv_home, + env.user)) + run("echo 'source /usr/local/bin/virtualenvwrapper.sh' >> " + "/home/%s/.bashrc" % env.user) + print(green("Successfully set up git, mercurial, pip, virtualenv, " + "supervisor, memcached.", bold=True)) + + +@task +@log_call +def create(): + """ + Creates the environment needed to host the project. + The environment consists of: system locales, virtualenv, database, project + files, SSL certificate, and project-specific Python requirements. + """ + # Generate project locale + locale = env.locale.replace("UTF-8", "utf8") + with hide("stdout"): + if locale not in run("locale -a"): + sudo("locale-gen %s" % env.locale) + sudo("update-locale %s" % env.locale) + sudo("service postgresql restart") + run("exit") + + # Create project path + run("mkdir -p %s" % env.proj_path) + + # Set up virtual env + run("mkdir -p %s" % env.venv_home) + with cd(env.venv_home): + if exists(env.proj_name): + if confirm("Virtualenv already exists in host server: %s" + "\nWould you like to replace it?" % env.proj_name): + run("rm -rf %s" % env.proj_name) + else: + abort() + run("virtualenv %s" % env.proj_name) + + # Upload project files + if env.deploy_tool in env.vcs_tools: + vcs_upload() + else: + rsync_upload() + + # Create DB and DB user + pw = db_pass() + user_sql_args = (env.proj_name, pw.replace("'", "\'")) + user_sql = "CREATE USER %s WITH ENCRYPTED PASSWORD '%s';" % user_sql_args + psql(user_sql, show=False) + shadowed = "*" * len(pw) + print_command(user_sql.replace("'%s'" % pw, "'%s'" % shadowed)) + psql("CREATE DATABASE %s WITH OWNER %s ENCODING = 'UTF8' " + "LC_CTYPE = '%s' LC_COLLATE = '%s' TEMPLATE template0;" % + (env.proj_name, env.proj_name, env.locale, env.locale)) + + # Set up SSL certificate + if not env.ssl_disabled: + conf_path = "/etc/nginx/conf" + if not exists(conf_path): + sudo("mkdir %s" % conf_path) + with cd(conf_path): + crt_file = env.proj_name + ".crt" + key_file = env.proj_name + ".key" + if not exists(crt_file) and not exists(key_file): + try: + crt_local, = glob(join("deploy", "*.crt")) + key_local, = glob(join("deploy", "*.key")) + except ValueError: + parts = (crt_file, key_file, env.domains[0]) + sudo("openssl req -new -x509 -nodes -out %s -keyout %s " + "-subj '/CN=%s' -days 3650" % parts) + else: + upload_template(crt_local, crt_file, use_sudo=True) + upload_template(key_local, key_file, use_sudo=True) + + # Install project-specific requirements + upload_template_and_reload("settings") + with project(): + if env.reqs_path: + pip("-r %s/%s" % (env.proj_path, env.reqs_path)) + pip("gunicorn setproctitle psycopg2 " + "django-compressor python-memcached") + # Bootstrap the DB + manage("createdb --noinput --nodata") + python("from django.conf import settings;" + "from django.contrib.sites.models import Site;" + "Site.objects.filter(id=settings.SITE_ID).update(domain='%s');" + % env.domains[0]) + for domain in env.domains: + python("from django.contrib.sites.models import Site;" + "Site.objects.get_or_create(domain='%s');" % domain) + if env.admin_pass: + pw = env.admin_pass + user_py = ("from django.contrib.auth import get_user_model;" + "User = get_user_model();" + "u, _ = User.objects.get_or_create(username='admin');" + "u.is_staff = u.is_superuser = True;" + "u.set_password('%s');" + "u.save();" % pw) + python(user_py, show=False) + shadowed = "*" * len(pw) + print_command(user_py.replace("'%s'" % pw, "'%s'" % shadowed)) + + return True + + +@task +@log_call +def remove(): + """ + Blow away the current project. + """ + if exists(env.venv_path): + run("rm -rf %s" % env.venv_path) + if exists(env.proj_path): + run("rm -rf %s" % env.proj_path) + for template in get_templates().values(): + remote_path = template["remote_path"] + if exists(remote_path): + 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) + + +############## +# Deployment # +############## + +@task +@log_call +def restart(): + """ + Restart gunicorn worker processes for the project. + If the processes are not running, they will be started. + """ + pid_path = "%s/gunicorn.pid" % env.proj_path + if exists(pid_path): + run("kill -HUP `cat %s`" % pid_path) + else: + sudo("supervisorctl update") + + +@task +@log_call +def deploy(): + """ + Deploy latest version of the project. + Backup current version of the project, push latest version of the project + via version control or rsync, install new requirements, sync and migrate + the database, collect any new static assets, and restart gunicorn's worker + processes for the project. + """ + if not exists(env.proj_path): + if confirm("Project does not exist in host server: %s" + "\nWould you like to create it?" % env.proj_name): + create() + else: + abort() + + # Backup current version of the project + with cd(env.proj_path): + backup("last.db") + if env.deploy_tool in env.vcs_tools: + with cd(env.repo_path): + if env.deploy_tool == "git": + run("git rev-parse HEAD > %s/last.commit" % env.proj_path) + elif env.deploy_tool == "hg": + run("hg id -i > last.commit") + with project(): + static_dir = static() + if exists(static_dir): + run("tar -cf static.tar --exclude='*.thumbnails' %s" % + static_dir) + else: + with cd(join(env.proj_path, "..")): + excludes = ["*.pyc", "*.pio", "*.thumbnails"] + exclude_arg = " ".join("--exclude='%s'" % e for e in excludes) + run("tar -cf {0}.tar {1} {0}".format(env.proj_name, exclude_arg)) + + # Deploy latest version of the project + with update_changed_requirements(): + if env.deploy_tool in env.vcs_tools: + vcs_upload() + else: + rsync_upload() + with project(): + manage("collectstatic -v 0 --noinput") + manage("syncdb --noinput") + manage("migrate --noinput") + for name in get_templates(): + upload_template_and_reload(name) + restart() + return True + + +@task +@log_call +def rollback(): + """ + Reverts project state to the last deploy. + When a deploy is performed, the current state of the project is + backed up. This includes the project files, the database, and all static + files. Calling rollback will revert all of these to their state prior to + the last deploy. + """ + with update_changed_requirements(): + if env.deploy_tool in env.vcs_tools: + with cd(env.repo_path): + if env.deploy_tool == "git": + run("GIT_WORK_TREE={0} git checkout -f " + "`cat {0}/last.commit`".format(env.proj_path)) + elif env.deploy_tool == "hg": + run("hg update -C `cat last.commit`") + with project(): + with cd(join(static(), "..")): + run("tar -xf %s/static.tar" % env.proj_path) + else: + with cd(env.proj_path.rsplit("/", 1)[0]): + run("rm -rf %s" % env.proj_name) + run("tar -xf %s.tar" % env.proj_name) + with cd(env.proj_path): + restore("last.db") + restart() + + +@task +@log_call +def all(): + """ + Installs everything required on a new system and deploy. + From the base software, up to the deployed project. + """ + install() + if create(): + deploy() diff --git a/diggersdigest/manage.py b/diggersdigest/manage.py old mode 100755 new mode 100644 index 24fd72b..32f1de8 --- a/diggersdigest/manage.py +++ b/diggersdigest/manage.py @@ -3,7 +3,11 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "diggersdigest.settings") + + from mezzanine.utils.conf import real_project_name + + settings_module = "%s.settings" % real_project_name("diggersdigest") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", settings_module) from django.core.management import execute_from_command_line diff --git a/diggersdigest/records/migrations/0002_artist_country_label_record.py b/diggersdigest/records/migrations/0002_artist_country_label_record.py new file mode 100644 index 0000000..fa32ff4 --- /dev/null +++ b/diggersdigest/records/migrations/0002_artist_country_label_record.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import mezzanine.core.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('shop', '0005_auto_20150527_1127'), + ('records', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Artist', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=128)), + ], + ), + migrations.CreateModel( + name='Country', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=128)), + ], + ), + migrations.CreateModel( + name='Label', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=128)), + ], + ), + migrations.CreateModel( + name='Record', + fields=[ + ('product_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='shop.Product')), + ('artiste', models.CharField(max_length=128)), + ('new', models.IntegerField()), + ('label', models.CharField(max_length=128)), + ('date', models.CharField(max_length=8)), + ('pays', models.CharField(max_length=128)), + ('desc', models.TextField()), + ('cover', models.IntegerField()), + ('vinyl', models.IntegerField()), + ('audio', mezzanine.core.fields.FileField(max_length=200, verbose_name='Audio File')), + ], + options={ + 'abstract': False, + }, + bases=('shop.product',), + ), + ] diff --git a/diggersdigest/records/models.py b/diggersdigest/records/models.py index 55cdea0..ea0f532 100644 --- a/diggersdigest/records/models.py +++ b/diggersdigest/records/models.py @@ -1,7 +1,18 @@ from __future__ import unicode_literals from django.db import models +from django.utils.translation import ugettext_lazy as _ +from cartridge.shop.models import Product + +from mezzanine.core.fields import FileField +from mezzanine.utils.models import upload_to + +# Auto-generated Django models with manage.py inspectdb on the old database +# You'll have to do the following manually to clean this up: +# * Make sure each model has one field with primary_key=True +# * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table +# Feel free to rename the models, but don't rename db_table values or field names. class Gallery(models.Model): titre = models.CharField(max_length=124) @@ -21,6 +32,10 @@ class Gallery(models.Model): managed = False db_table = 'gallery' + def __unicode__(self): + return self.titre + + class Grading(models.Model): nom = models.CharField(max_length=5) @@ -29,6 +44,10 @@ class Grading(models.Model): managed = False db_table = 'grading' + def __unicode__(self): + return self.nom + + class Link(models.Model): section = models.IntegerField() @@ -41,6 +60,9 @@ class Link(models.Model): managed = False db_table = 'links' + def __unicode__(self): + return self.titre + class Mix(models.Model): genre = models.CharField(max_length=128) @@ -56,6 +78,9 @@ class Mix(models.Model): managed = False db_table = 'mix' + def __unicode__(self): + return self.titre + class News(models.Model): titre = models.CharField(max_length=128) @@ -72,6 +97,9 @@ class News(models.Model): managed = False db_table = 'news' + def __unicode__(self): + return self.titre + class Shop(models.Model): theme = models.IntegerField() @@ -95,6 +123,9 @@ class Shop(models.Model): managed = False db_table = 'shop' + def __unicode__(self): + return self.titre + class Theme(models.Model): nom = models.CharField(max_length=124) @@ -105,6 +136,9 @@ class Theme(models.Model): managed = False db_table = 'theme' + def __unicode__(self): + return self.nom + class User(models.Model): username = models.CharField(max_length=10) @@ -114,3 +148,50 @@ class User(models.Model): class Meta: managed = False db_table = 'user' + + def __unicode__(self): + return self.username + + +# New models for the 'records' app + +class Artist(models.Model): + name = models.CharField(max_length=128) + def __unicode__(self): + return self.name + +class Label(models.Model): + name = models.CharField(max_length=128) + def __unicode__(self): + return self.name + +class Country(models.Model): + name = models.CharField(max_length=128) + def __unicode__(self): + return self.name + + + +class Record(Product): + """ + Model for Record + """ + # herited fields (from Product): + # title --> shop.titre + # categories --> shop.theme + # price --> shop.prix + + artiste = models.CharField(max_length=128) + new = models.IntegerField() + label = models.CharField(max_length=128) + date = models.CharField(max_length=8) + pays = models.CharField(max_length=128) + desc = models.TextField() + cover = models.IntegerField() # TODO : choices=GRADINGS) + vinyl = models.IntegerField() # TODO : choices=GRADING) + audio = FileField(_("Audio File"), max_length=200, format="Audio", + upload_to=upload_to("dig2site.Record.audio", "audio")) + + def __unicode__(self): + return " - ".join([self.artiste, self.title]) + diff --git a/diggersdigest/requirements.txt b/diggersdigest/requirements.txt new file mode 100644 index 0000000..1b81a5b --- /dev/null +++ b/diggersdigest/requirements.txt @@ -0,0 +1,3 @@ +Django==1.8.3 +MySQL-python==1.2.5 +cartridge==0.10.0