diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/conf/newrelic.ini b/app/conf/newrelic.ini new file mode 100644 index 0000000..f4c9df9 --- /dev/null +++ b/app/conf/newrelic.ini @@ -0,0 +1,210 @@ +# --------------------------------------------------------------------------- + +# +# This file configures the New Relic Python Agent. +# +# The path to the configuration file should be supplied to the function +# newrelic.agent.initialize() when the agent is being initialized. +# +# The configuration file follows a structure similar to what you would +# find for Microsoft Windows INI files. For further information on the +# configuration file format see the Python ConfigParser documentation at: +# +# http://docs.python.org/library/configparser.html +# +# For further discussion on the behaviour of the Python agent that can +# be configured via this configuration file see: +# +# http://newrelic.com/docs/python/python-agent-configuration +# + +# --------------------------------------------------------------------------- + +# Here are the settings that are common to all environments. + +[newrelic] + +# You must specify the license key associated with your New +# Relic account. This key binds the Python Agent's data to your +# account in the New Relic service. +license_key = 7a0c5c3cda7e345c16a596d31a7f1fe8dc9c2ee7 + +# The appplication name. Set this to be the name of your +# application as you would like it to show up in New Relic UI. +# The UI will then auto-map instances of your application into a +# entry on your home dashboard page. +app_name = Transcribe.ninja + +# When "true", the agent collects performance data about your +# application and reports this data to the New Relic UI at +# newrelic.com. This global switch is normally overridden for +# each environment below. +monitor_mode = true + +# Sets the name of a file to log agent messages to. Useful for +# debugging any issues with the agent. This is not set by +# default as it is not known in advance what user your web +# application processes will run as and where they have +# permission to write to. Whatever you set this to you must +# ensure that the permissions for the containing directory and +# the file itself are correct, and that the user that your web +# application runs as can write to the file. If not able to +# write out a log file, it is also possible to say "stderr" and +# output to standard error output. This would normally result in +# output appearing in your web server log. +#log_file = /tmp/newrelic-python-agent.log + +# Sets the level of detail of messages sent to the log file, if +# a log file location has been provided. Possible values, in +# increasing order of detail, are: "critical", "error", "warning", +# "info" and "debug". When reporting any agent issues to New +# Relic technical support, the most useful setting for the +# support engineers is "debug". However, this can generate a lot +# of information very quickly, so it is best not to keep the +# agent at this level for longer than it takes to reproduce the +# problem you are experiencing. +log_level = info + +# The Python Agent communicates with the New Relic service using +# SSL by default. Note that this does result in an increase in +# CPU overhead, over and above what would occur for a non SSL +# connection, to perform the encryption involved in the SSL +# communication. This work is though done in a distinct thread +# to those handling your web requests, so it should not impact +# response times. You can if you wish revert to using a non SSL +# connection, but this will result in information being sent +# over a plain socket connection and will not be as secure. +ssl = true + +# High Security Mode enforces certain security settings, and +# prevents them from being overridden, so that no sensitive data +# is sent to New Relic. Enabling High Security Mode means that +# SSL is turned on, request parameters are not collected, and SQL +# can not be sent to New Relic in its raw form. To activate High +# Security Mode, it must be set to 'true' in this local .ini +# configuration file AND be set to 'true' in the server-side +# configuration in the New Relic user interface. For details, see +# https://docs.newrelic.com/docs/subscriptions/high-security +high_security = false + +# The Python Agent will attempt to connect directly to the New +# Relic service. If there is an intermediate firewall between +# your host and the New Relic service that requires you to use a +# HTTP proxy, then you should set both the "proxy_host" and +# "proxy_port" settings to the required values for the HTTP +# proxy. The "proxy_user" and "proxy_pass" settings should +# additionally be set if proxy authentication is implemented by +# the HTTP proxy. The "proxy_scheme" setting dictates what +# protocol scheme is used in talking to the HTTP proxy. This +# would normally always be set as "http" which will result in the +# agent then using a SSL tunnel through the HTTP proxy for end to +# end encryption. +# proxy_scheme = http +# proxy_host = hostname +# proxy_port = 8080 +# proxy_user = +# proxy_pass = + +# Tells the transaction tracer and error collector (when +# enabled) whether or not to capture the query string for the +# URL and send it as the request parameters for display in the +# UI. When "true", it is still possible to exclude specific +# values from being captured using the "ignored_params" setting. +capture_params = false + +# Space separated list of variables that should be removed from +# the query string captured for display as the request +# parameters in the UI. +ignored_params = + +# The transaction tracer captures deep information about slow +# transactions and sends this to the UI on a periodic basis. The +# transaction tracer is enabled by default. Set this to "false" +# to turn it off. +transaction_tracer.enabled = true + +# Threshold in seconds for when to collect a transaction trace. +# When the response time of a controller action exceeds this +# threshold, a transaction trace will be recorded and sent to +# the UI. Valid values are any positive float value, or (default) +# "apdex_f", which will use the threshold for a dissatisfying +# Apdex controller action - four times the Apdex T value. +transaction_tracer.transaction_threshold = apdex_f + +# When the transaction tracer is on, SQL statements can +# optionally be recorded. The recorder has three modes, "off" +# which sends no SQL, "raw" which sends the SQL statement in its +# original form, and "obfuscated", which strips out numeric and +# string literals. +transaction_tracer.record_sql = obfuscated + +# Threshold in seconds for when to collect stack trace for a SQL +# call. In other words, when SQL statements exceed this +# threshold, then capture and send to the UI the current stack +# trace. This is helpful for pinpointing where long SQL calls +# originate from in an application. +transaction_tracer.stack_trace_threshold = 0.5 + +# Determines whether the agent will capture query plans for slow +# SQL queries. Only supported in MySQL and PostgreSQL. Set this +# to "false" to turn it off. +transaction_tracer.explain_enabled = true + +# Threshold for query execution time below which query plans +# will not not be captured. Relevant only when "explain_enabled" +# is true. +transaction_tracer.explain_threshold = 0.5 + +# Space separated list of function or method names in form +# 'module:function' or 'module:class.function' for which +# additional function timing instrumentation will be added. +transaction_tracer.function_trace = + +# The error collector captures information about uncaught +# exceptions or logged exceptions and sends them to UI for +# viewing. The error collector is enabled by default. Set this +# to "false" to turn it off. +error_collector.enabled = true + +# To stop specific errors from reporting to the UI, set this to +# a space separated list of the Python exception type names to +# ignore. The exception name should be of the form 'module:class'. +error_collector.ignore_errors = + +# Browser monitoring is the Real User Monitoring feature of the UI. +# For those Python web frameworks that are supported, this +# setting enables the auto-insertion of the browser monitoring +# JavaScript fragments. +browser_monitoring.auto_instrument = true + +# A thread profiling session can be scheduled via the UI when +# this option is enabled. The thread profiler will periodically +# capture a snapshot of the call stack for each active thread in +# the application to construct a statistically representative +# call tree. +thread_profiler.enabled = true + +# --------------------------------------------------------------------------- + +# +# The application environments. These are specific settings which +# override the common environment settings. The settings related to a +# specific environment will be used when the environment argument to the +# newrelic.agent.initialize() function has been defined to be either +# "development", "test", "staging" or "production". +# + +[newrelic:development] +monitor_mode = false + +[newrelic:test] +monitor_mode = false + +[newrelic:staging] +app_name = Zuykov.com +monitor_mode = true + +[newrelic:production] +monitor_mode = true + +# --------------------------------------------------------------------------- diff --git a/app/conf/nginx.conf.template b/app/conf/nginx.conf.template new file mode 100644 index 0000000..be5282d --- /dev/null +++ b/app/conf/nginx.conf.template @@ -0,0 +1,22 @@ +upstream django_zuykov { + server unix:///home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/wsgi_transcribe_ninja.sock; +} + +server { + listen 80; + server_name dev.zuykov.com; + charset utf-8; + + client_max_body_size 1024M; + + location /static { + alias /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/static; + + } + + location / { + uwsgi_pass django_zuykov; + include /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/uwsgi_params; + } + +} \ No newline at end of file diff --git a/app/conf/uwsgi_params b/app/conf/uwsgi_params new file mode 100644 index 0000000..2a45908 --- /dev/null +++ b/app/conf/uwsgi_params @@ -0,0 +1,15 @@ +uwsgi_param QUERY_STRING $query_string; +uwsgi_param REQUEST_METHOD $request_method; +uwsgi_param CONTENT_TYPE $content_type; +uwsgi_param CONTENT_LENGTH $content_length; + +uwsgi_param REQUEST_URI $request_uri; +uwsgi_param PATH_INFO $document_uri; +uwsgi_param DOCUMENT_ROOT $document_root; +uwsgi_param SERVER_PROTOCOL $server_protocol; +uwsgi_param HTTPS $https if_not_empty; + +uwsgi_param REMOTE_ADDR $remote_addr; +uwsgi_param REMOTE_PORT $remote_port; +uwsgi_param SERVER_PORT $server_port; +uwsgi_param SERVER_NAME $server_name; diff --git a/app/conf/uwsgi_zuykov.conf.template b/app/conf/uwsgi_zuykov.conf.template new file mode 100644 index 0000000..e7a9bb9 --- /dev/null +++ b/app/conf/uwsgi_zuykov.conf.template @@ -0,0 +1,14 @@ +[uwsgi] +uid=%(SERVER_USERNAME)s +gid=%(SERVER_USERNAME)s + +chdir = /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s +module = app.wsgi_zuykov +home = /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/env +eval = import newrelic.agent, wsgi_zuykov; application = newrelic.agent.wsgi_application()(wsgi_zuykov.application) + +master = true +processes = 4 +socket = /home/%(EC2_SERVER_USERNAME)s/%(PROJECT_NAME)s/app/wsgi_zuykov.sock +chmod-socket = 666 +vacuum = true diff --git a/app/deploy/__init__.py b/app/deploy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/deploy/debian.py b/app/deploy/debian.py new file mode 100644 index 0000000..fcdd7ac --- /dev/null +++ b/app/deploy/debian.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from app import settings + +from deployer.node import Node +from deployer.utils import esc1 + + +class Debian(Node): + def _configure_instance(self, tasks): + # Configure the instance that was just created + for item in tasks: + try: + print item['message'] + except KeyError: + pass + + getattr(self, "_" + item['action'])(item['params']) + + # Actions + # def _virtualenv(params): + # """ + # Allows running commands on the server + # with an active virtualenv + # """ + # with cd(fabconf['APPS_DIR']): + # _virtualenv_command(_render(params)) + + def _apt(self, params): + """ + Runs apt-get install commands + """ + for pkg in params: + self._sudo("apt-get install -y -qq %s" % pkg) + + def _pip(self, params): + """ + Runs pip install commands + """ + for pkg in params: + self._sudo("pip install %s" % pkg) + + def _run(self, params): + """ + Runs command with active user + """ + command = self._render(params) + self.hosts.run(command) + + def _sudo(self, params): + """ + Run command as root + """ + command = self._render(params) + self.hosts.sudo(command) + + def _put(self, params): + """ + Moves a file from local computer to server + """ + for host in self.hosts.get_hosts(): + host = host() + host.put_file( + self._render(params['file']), + self._render(params['destination']) + ) + + def _put_template(self, params, context=settings.__dict__): + """ + Same as _put() but it loads a file and does variable replacement + """ + f = open(self._render(params['template']), 'r') + template = f.read() + + self.hosts.run( + self._write_to( + self._render(template, context), + self._render(params['destination']) + ) + ) + + def _render(self, template, context=settings.__dict__): + """ + Does variable replacement + """ + return template % context + + def _write_to(self, string, path): + """ + Writes a string to a file on the server + """ + return "echo '" + string + "' > " + path + + def _append_to(self, string, path): + """ + Appends to a file on the server + """ + return "echo '" + string + "' >> " + path + + def _virtualenv(self, command): + """ + Activates virtualenv and runs command + """ + with self.hosts.prefix(settings.ACTIVATE): + with self.hosts.cd(settings.PROJECT_DIR, expand=True): + self.hosts.run(self._render(command)) diff --git a/app/deploy/django.py b/app/deploy/django.py new file mode 100644 index 0000000..5e85baf --- /dev/null +++ b/app/deploy/django.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# from app import settings + +# from deployer.node import Node +from deployer.utils import esc1 + +from app.deploy.debian import Debian + + +class DjangoDeployment(Debian): + def run_management_command(self, command): + """ Run Django management command in virtualenv. """ + self._virtualenv('./manage.py %s' % esc1(command)) + + def django_shell(self): + """ Open interactive Django shell. """ + self.run_management_command('shell') + + def python_packages_install(self): + """ Run Django management command in virtualenv. """ + # Activate the virtualenv. + self._virtualenv('pip install --upgrade pip && pip install -r %(PROJECT_DIR)s/requirements.txt --upgrade' % settings.__dict__) diff --git a/app/deploy/hosts.py b/app/deploy/hosts.py new file mode 100644 index 0000000..cf3bbf7 --- /dev/null +++ b/app/deploy/hosts.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Deploy +from deployer.host import SSHHost + +from zsite import settings + + +class WebHost(SSHHost): + slug = 'web' + address = 'dev.zuykov.com' + username = 'web' + password = '9oijffDf2vi@D!' + +class DatabaseHost(WebHost): + slug = 'database' diff --git a/app/deploy/service.py b/app/deploy/service.py new file mode 100644 index 0000000..74919bc --- /dev/null +++ b/app/deploy/service.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from deployer.node import Node, required_property +from deployer.utils import esc1 + + +class UpstartService(Node): + + """ + Abstraction for any upstart service with start/stop/status methods. + """ + name = required_property() + + def start(self): + self.hosts.sudo('service %s start' % esc1(self.name)) + + def stop(self): + self.hosts.sudo('service %s stop' % esc1(self.name)) + + def restart(self): + self.stop() + self.start() + + def status(self): + self.hosts.sudo('service %s status' % esc1(self.name)) diff --git a/app/deploy/tasks.py b/app/deploy/tasks.py new file mode 100644 index 0000000..d3d1ef1 --- /dev/null +++ b/app/deploy/tasks.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +''' +-------------------------------------------------------------------------------------- +tasks.py +-------------------------------------------------------------------------------------- +A set of tasks to manage your AWS Django deployment. + +author : Ashok Fernandez (github.com/ashokfernandez/) +credit : Derived from files in https://github.com/gcollazo/Fabulous +date : 11 / 3 / 2014 + +Tasks include: + - configure_instance : Configures a new EC2 instance (as definied in settings.py) and return's it's public dns + This takes around 8 minutes to complete. + + - update_packages : Updates the python packages on the server to match those found in requirements/common.txt and + requirements/prod.txt + + - deploy : Pulls the latest commit from the master branch on the server, collects the static files, syncs the db and + restarts the server + + - reload_gunicorn : Pushes the gunicorn startup script to the servers and restarts the gunicorn process, use this if you + have made changes to templates/start_gunicorn.bash + + - reload_nginx : Pushes the nginx config files to the servers and restarts the nginx, use this if you + have made changes to templates/nginx-app-proxy or templates/nginx.conf + + - reload_supervisor : Pushes the supervisor config files to the servers and restarts the supervisor, use this if you + have made changes to templates/supervisord-init or templates/supervisord.conf + +''' + +# Spawns a new EC2 instance (as definied in djangofab_conf.py) and return's it's public dns +# This takes around 8 minutes to complete. +common_configure = [ + # First command as regular user + {"action": "run", "params": "whoami"}, + + # List of APT packages to install + {"action": "apt", + "params": ["libpq-dev", "git", + "python-setuptools", "python-dev", "build-essential", "python-pip", "redis-server", + "libmysqlclient-dev", "subversion"], + "message":"Installing apt-get packages" }, + + # List of pypi packages to install + {"action": "pip", "params": ["virtualenv"], + "message":"Installing virtualenv"}, + + #project directory + {"action": "run", "params": "mkdir -p %(PROJECT_DIR)s", "message": "Create project folder" }, + {"action": "run", "params": "mkdir -p %(LOGS_DIR)s", "message": "Create logs folder" }, + {"action": "sudo", "params": "chown -R %(SERVER_USERNAME)s: %(PROJECT_DIR)s"}, + + # git setup + {"action": "run", "params": "git config --global user.name '%(GIT_USERNAME)s'", + "message": "Configuring git"}, + {"action": "run", + "params": "git config --global user.email '%(ADMIN_EMAIL)s'"}, + {"action": "put", "params": {"file": "%(GIT_KEY_PATH)s", + "destination": "/home/%(SERVER_USERNAME)s/.ssh/%(GIT_KEY_NAME)s"}}, + {"action": "run", "params": + "chmod 600 /home/%(SERVER_USERNAME)s/.ssh/%(GIT_KEY_NAME)s"}, + {"action": "run", "params": + u"""echo 'IdentityFile /home/%(SERVER_USERNAME)s/.ssh/%(GIT_KEY_NAME)s' >> /home/%(SERVER_USERNAME)s/.ssh/config"""}, + {"action": "run", "params": + "ssh-keyscan github.com >> /home/%(SERVER_USERNAME)s/.ssh/known_hosts"}, + + # Clone the git repo + {"action": "run", + "params": "git clone %(REPOSITORY)s %(PROJECT_DIR)s"}, + + # virtualenv + {"action": "run", + "params": "mkdir -p %(ENV_DIR)s", "message": "Create project folder" }, + {"action": "run", + "params": "virtualenv %(ENV_DIR)s", "message": "Configuring virtualenv" }, + + {"action": "sudo", "params": "chown -R %(SERVER_USERNAME)s: %(ENV_DIR)s"}, + # {"action": "run", "params": + # "echo 'expo WORKON_HOME=%(PRO_DIR)s' >> /home/%(SERVER_USERNAME)s/.profile"}, + {"action": "run", "params": + u"echo 'source %(ENV_DIR)s/bin/activate' >> /home/%(SERVER_USERNAME)s/.profile"}, + {"action": "run", "params": "source /home/%(SERVER_USERNAME)s/.profile"}, + + {"action": "virtualenv", "params": + "pip install -r %(PROJECT_DIR)s/requirements.txt --upgrade"}, +] + +web_configure = [ + # List of APT packages to install + {"action": "apt", + "params": ["nginx", "uwsgi", "uwsgi-plugin-python", "nodejs", "npm"], + "message":"Installing nginx, uwsgi, nodejs packages"}, + {"action": "sudo", "params": "npm install -g bower karma grunt grunt-cli"}, + # Костыль с нодой + {"action": "run", "params": "ln -s /usr/bin/nodejs %(ENV_DIR)s/bin/node"}, + {"action": "sudo", "params": "cd %(PROJECT_DIR)s/frontend/transcribe.ninja && npm install"}, + {"action": "sudo", "params": "cd %(PROJECT_DIR)s/frontend/stenograph.us && npm install"}, + {"action": "sudo", "params": "rm -rf /etc/nginx/sites-enabled/default"}, + {"action": "sudo", "params": "rm -rf /etc/supervisor/conf.d/default"}, + {"action": "sudo", "params": "rm -rf /etc/uwsgi/apps-enabled/default.ini"}, +] + +npm_install = [ + {"action": "sudo", "params": "cd %(PROJECT_DIR)s/frontend/transcribe.ninja && npm install"}, + {"action": "sudo", "params": "cd %(PROJECT_DIR)s/frontend/stenograph.us && npm install"}, +] + + # nginx +reload_nginx = [ + {"action": "put_template", "params": {"template": "%(BASE_DIR)s/app/conf/nginx.conf.template", + "destination": "/home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/nginx.conf"}}, + {"action": "sudo", "params": "service nginx restart", + "message": "Restarting nginx"}, +] + +create_nginx_links = [ + {"action": "sudo", "params": + "ln -s /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/nginx.conf /etc/nginx/sites-enabled/%(PROJECT_NAME)s.conf"}, +] + +create_uwsgi_links = [ + {"action": "sudo", "params": + "ln -s /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/uwsgi_stenograph_us.conf /etc/uwsgi/apps-enabled/%(PROJECT_NAME)s_stenograph_us.ini"}, + {"action": "sudo", "params": + "ln -s /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/uwsgi_transcribe_ninja.conf /etc/uwsgi/apps-enabled/%(PROJECT_NAME)s_transcribe_ninja.ini"}, +] + +reload_uwsgi = [ + {"action": "put_template", "params": {"template": "%(BASE_DIR)s/app/conf/uwsgi_stenograph_us.conf.template", + "destination": "/home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/uwsgi_stenograph_us.conf"}}, + {"action": "put_template", "params": {"template": "%(BASE_DIR)s/app/conf/uwsgi_transcribe_ninja.conf.template", + "destination": "/home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/uwsgi_transcribe_ninja.conf"}}, + {"action": "sudo", "params": "service uwsgi restart", + "message": "Restarting uwsgi"}, +] + +web_configure += reload_uwsgi + reload_nginx + create_uwsgi_links + create_nginx_links + +engine_configure = [ + # List of pypi packages to install + {"action": "run", "params": "cp -r ~/%(PROJECT_NAME)s/env/share/voiceid ~/%(PROJECT_NAME)s/env/local/share/", + "message":"Move voiceid"}, + + {"action": "apt", "params": ["supervisor"], + "message":"Installing supervisor"}, + + {"action": "put_template", "params": {"template": "%(BASE_DIR)s/app/conf/supervisor.conf.template", + "destination": "/home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/supervisor.conf"}}, + {"action": "sudo", "params": + "ln -s /home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/supervisor.conf /etc/supervisor/conf.d/%(PROJECT_NAME)s.conf"}, + {"action": "sudo", "params": "service supervisor restart", + "message": "Restarting supervisor"}, + + # Запстить очереди + # TODO: настроить редис как внешний сервер +] + +reload_supervisor = [ + {"action": "put_template", "params": {"template": "%(BASE_DIR)s/app/conf/supervisor.conf.template", + "destination": "/home/%(SERVER_USERNAME)s/%(PROJECT_NAME)s/app/conf/supervisor.conf"}}, + {"action": "sudo", "params": "service supervisor restart", + "message": "Restarting supervisor"}, +] diff --git a/app/deploy/zuykov.py b/app/deploy/zuykov.py new file mode 100644 index 0000000..c0c2fc5 --- /dev/null +++ b/app/deploy/zuykov.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# from django.db import connection + +from deployer.node import Node, map_roles +from deployer.utils import esc1 + +from app.deploy.service import * +from app.deploy.django import * +import app.deploy.tasks as tasks + +import os.path + +from app import settings + + +class ZuykovWebSystem(Node): + + """ + The base definition of our web system. + + roles: web, engine, database + """ + + # @map_roles(host='database') + # class Database(<): + # def create(self): + # host = settings.DATABASES['default'] + + # # Создаём группу безопасности + # security_group = self._ec2_mysql_security_group() + # # Создаём группу параметров + + # # Запускаем инстанс + # self._rds_create_instance( + # 'database', + # host['NAME'], + # host['USER'], + # host['PASSWORD']) + + + @map_roles(host='web') + class Application(DjangoDeployment): + def configure_base(self): + self._configure_instance(tasks.common_configure) + + def hello(self): + self.hosts.run('echo hello world') + + def update(self): + self.checkout() + self.pull() + + def checkout(self, commit="."): + with self.hosts.cd(settings.PROJECT_DIR, expand=True): + self.hosts.run("git checkout '%s'" % esc1(commit)) + + def pull(self): + with self.hosts.cd(settings.PROJECT_DIR, expand=True): + self.hosts.run('git pull') + + + def create(self): + self.Application.hello() + # Если созданы — удалить + # # Предварительно спросить + # self.Frontend.create() + + # self.Engine.create() + + + + def deploy(self): + pass + # self.Application.update() + + # self.Application.python_packages_install() + + # self.update_hosts() + + # self.Engine.migrate() + + # self.Frontend.restart() + + # self.Engine.restart() + + diff --git a/zsite/settings.py b/app/settings.py similarity index 92% rename from zsite/settings.py rename to app/settings.py index 836f705..afc5b77 100644 --- a/zsite/settings.py +++ b/app/settings.py @@ -36,6 +36,19 @@ ALLOWED_HOSTS = [] # Application definition +PROJECT_NAME = 'zuykov' +SERVER_USERNAME = 'web' + +PROJECT_DIR = '~/' + PROJECT_NAME +ENV_DIR = '%s/env' % PROJECT_DIR +LOGS_DIR = '~/logs' +ACTIVATE = '. %s/bin/activate' % ENV_DIR + +REPOSITORY = 'git@github.com:fefa4ka/zuykov.git' +GIT_USERNAME = 'fefa4ka' +ADMIN_EMAIL = 'fefa4ka@gmail.com' +GIT_KEY_PATH = '/Users/fefa4ka/.ssh/deploy_rsa' +GIT_KEY_NAME = 'github_rsa' @@ -87,13 +100,17 @@ PIPELINE_JS = { 'main': { 'source_filenames': ( 'js/app.js', + 'js/forms.js', + ), 'output_filename': 'js/*.js', }, 'vendor': { 'source_filenames': ( 'vendor/angular/angular.js', - 'vendor/angular-bootstrap/ui-bootstrap.js' + 'vendor/angular-bootstrap/ui-bootstrap.js', + 'vendor/angular-bootstrap/ui-bootstrap-tpls.js', + 'vendor/jquery/dist/jquery.js', ), 'output_filename': 'js/vendor.js', } @@ -201,6 +218,7 @@ INSTALLED_APPS = ( 'aldryn_bootstrap3', + 'djangocms_forms', # 'debug_toolbar', ) diff --git a/zsite/urls.py b/app/urls.py similarity index 95% rename from zsite/urls.py rename to app/urls.py index 883cb1f..82b3ec1 100644 --- a/zsite/urls.py +++ b/app/urls.py @@ -9,12 +9,15 @@ from django.conf import settings admin.autodiscover() urlpatterns = i18n_patterns('', + url(r'^', include('djangocms_forms.urls')), url(r'^admin/', include(admin.site.urls)), # NOQA url(r'^sitemap\.xml$', 'django.contrib.sitemaps.views.sitemap', {'sitemaps': {'cmspages': CMSSitemap}}), url(r'^select2/', include('django_select2.urls')), url(r'^', include('cms.urls')), url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), + + ) # This is only needed when using runserver. diff --git a/blog/migrations/0009_auto_20150726_2021.py b/blog/migrations/0009_auto_20150726_2021.py new file mode 100644 index 0000000..2bd94b1 --- /dev/null +++ b/blog/migrations/0009_auto_20150726_2021.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0008_auto_20150713_1609'), + ] + + operations = [ + migrations.AlterModelOptions( + name='blogcategorytranslation', + options={'default_permissions': (), 'verbose_name': 'blog category Translation', 'managed': True}, + ), + migrations.AlterModelOptions( + name='posttranslation', + options={'default_permissions': (), 'verbose_name': 'blog article Translation', 'managed': True}, + ), + migrations.RemoveField( + model_name='latestpostsplugin', + name='tags', + ), + migrations.AlterField( + model_name='blogcategorytranslation', + name='language_code', + field=models.CharField(max_length=15, verbose_name='Language', db_index=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='blogconfig', + name='category_slug', + field=models.CharField(help_text='Only category to display', max_length=255, verbose_name='Category slug', blank=True), + preserve_default=True, + ), + migrations.AlterField( + model_name='post', + name='enable_comments', + field=models.BooleanField(default=True, verbose_name='Enable comments on post'), + preserve_default=True, + ), + migrations.AlterField( + model_name='posttranslation', + name='language_code', + field=models.CharField(max_length=15, verbose_name='Language', db_index=True), + preserve_default=True, + ), + migrations.AlterUniqueTogether( + name='blogconfigtranslation', + unique_together=set([('language_code', 'master')]), + ), + ] diff --git a/blog/urls.py b/blog/urls.py index 41cf7a4..5f3dcc5 100644 --- a/blog/urls.py +++ b/blog/urls.py @@ -1,10 +1,12 @@ # -*- coding: utf-8 -*- -from django.conf.urls import patterns, url +from django.conf.urls import patterns, include, url from .views import (PostListView, PostDetailView, TaggedListView, AuthorEntriesView, PostArchiveView, CategoryEntriesView) from .feeds import LatestEntriesFeed, TagFeed +from djangocms_forms.views import FormSubmission + urlpatterns = patterns( '', diff --git a/deploy.py b/deploy.py new file mode 100755 index 0000000..6e5d526 --- /dev/null +++ b/deploy.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from django.db import connection + +from deployer.client import start + +from deployer.node import Node + +from app.deploy import hosts +from app.deploy.zuykov import ZuykovWebSystem + +import app.settings as settings + + +class RootNode(Node): + + """ + The root node of our configuration, containing two 'instances' of + `WebSystem`, + """ + + class ProductionSystem(ZuykovWebSystem): + class Hosts: + web = {hosts.WebHost} + database = {hosts.DatabaseHost} + + +if __name__ == '__main__': + start(RootNode) diff --git a/manage.py b/manage.py index e75f64a..7223825 100755 --- a/manage.py +++ b/manage.py @@ -3,7 +3,7 @@ import os import sys if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "zsite.settings") + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings") from django.core.management import execute_from_command_line diff --git a/requirements.txt b/requirements.txt index acae9ed..abd5f2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,4 +19,6 @@ djangocms_video mysql django-pipeline django_debug_toolbar +djangocms-forms +django-multipleformwizard #export C_INCLUDE_PATH=/usr/local/Cellar/libxml2/2.9.2/include/libxml2:$C_INCLUDE_PATH \ No newline at end of file diff --git a/service/__init__.py b/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/service/cms_app.py b/service/cms_app.py new file mode 100644 index 0000000..cd9ea32 --- /dev/null +++ b/service/cms_app.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from aldryn_apphooks_config.app_base import CMSConfigApp +from cms.apphook_pool import apphook_pool +from django.utils.translation import ugettext_lazy as _ + +from .models import ServiceConfig + + +class BlogApp(CMSConfigApp): + app_config = ServiceConfig + name = _('Service') + urls = ['service.urls'] + app_name = 'service' + +apphook_pool.register(ServiceApp) diff --git a/service/cms_appconfig.py b/service/cms_appconfig.py new file mode 100644 index 0000000..1fd6305 --- /dev/null +++ b/service/cms_appconfig.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django import forms +from django.conf import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from aldryn_apphooks_config.utils import setup_config +from aldryn_apphooks_config.models import AppHookConfig +from app_data import AppDataForm +from parler.models import TranslatableModel +from parler.models import TranslatedFields + +from cms.models.fields import PlaceholderField + + +class ServiceConfig(TranslatableModel, AppHookConfig): + """Adds some translatable, per-app-instance fields.""" + translations = TranslatedFields( + app_title=models.CharField(_('application title'), max_length=234), + ) + + category_slug = models.CharField( + _('Category slug'), + blank=True, + max_length=255, + help_text=_('Only category to display')) + + placeholder_base_top = PlaceholderField( + 'service_base_top', + related_name='service_base_top', + ) + + placeholder_base_sidebar = PlaceholderField( + 'service_base_sidebar', + related_name='service_base_sidebar', + ) + + placeholder_list_top = PlaceholderField( + 'service_list_top', + related_name='service_list_top', + ) + + placeholder_list_footer = PlaceholderField( + 'service_list_footer', + related_name='service_list_footer', + ) + + placeholder_detail_top = PlaceholderField( + 'service_detail_top', + related_name='service_detail_top', + ) + + placeholder_detail_bottom = PlaceholderField( + 'service_detail_bottom', + related_name='service_detail_bottom', + ) + + placeholder_detail_footer = PlaceholderField( + 'service_detail_footer', + related_name='service_detail_footer', + ) + + def get_app_title(self): + return getattr(self, 'app_title', _('untitled')) + + +class SeviceConfigForm(AppDataForm): + default_published = forms.BooleanField( + label=_(u'Post published by default'), required=False, + initial=getattr(settings, 'service_DEFAULT_PUBLISHED', True)) + +setup_config(ServiceConfigForm, ServiceConfig) diff --git a/service/cms_plugins.py b/service/cms_plugins.py new file mode 100644 index 0000000..5a7302f --- /dev/null +++ b/service/cms_plugins.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +from django.utils.translation import ugettext_lazy as _ + +from cms.models.pluginmodel import CMSPlugin +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool + +from .models import ContextServicePlugin +from .forms import ContextServiceForm +from .settings import get_setting + + +class SevicePlugin(CMSPluginBase): + module = 'Sevice' + + +class SeviceContextPlugin(SevicePlugin): + """ + Non cached plugin which returns the latest posts taking into account the + user / toolbar state + """ + render_template = 'sevice/plugins/context_services.html' + name = _('Context Services') + model = ContextServicePlugin + form = ContextServiceForm + filter_horizontal = ('categories',) + cache = False + + def render(self, context, instance, placeholder): + context = super(SeviceContextPlugin, self).render(context, instance, placeholder) + context['services_list'] = instance.get_services(context['request']) + + if instance.categories.exists(): + context['category'] = instance.categories.all()[0].slug + else: + context['category'] = "all" + + return context + + +class SeviceContextPluginCached(SevicePlugin): + """ + Cached plugin which returns the latest published posts + """ + render_template = 'sevice/plugins/context_services.html' + name = _('Context Services') + model = ContextServicePlugin + form = ContextServiceForm + filter_horizontal = ('categories',) + + def render(self, context, instance, placeholder): + context = super(SeviceContextPluginCached, self).render(context, instance, placeholder) + context['posts_list'] = instance.get_posts() + + return context + + +plugin_pool.register_plugin(SeviceContextPlugin) diff --git a/service/urls.py b/service/urls.py new file mode 100644 index 0000000..9b8cd5e --- /dev/null +++ b/service/urls.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from django.conf.urls import patterns, include, url + +from .views import (ServiceDetailView) + + +urlpatterns = patterns( + '', + url(r'^(?P[\w\.@+-]+)/$', ServiceDetailView.as_view(), name='service-detail'), +) diff --git a/service/views.py b/service/views.py new file mode 100644 index 0000000..1d90738 --- /dev/null +++ b/service/views.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +from django.contrib.auth import get_user_model +from django.core.urlresolvers import resolve +from django.utils.timezone import now +from django.utils.translation import get_language +from django.views.generic import ListView, DetailView + +from parler.views import ViewUrlMixin, TranslatableSlugMixin + +from aldryn_apphooks_config.mixins import AppConfigMixin + +from .models import Service, ServiceCategory, SERVICE_CURRENT_SERVICE_IDENTIFIER +from .settings import get_setting + +User = get_user_model() + + +class BaseServiceView(ViewUrlMixin, AppConfigMixin): + + def get_queryset(self): + language = get_language() + queryset = self.model._default_manager.active_translations(language_code=language) + + print self.config + if self.config and self.config.category_slug: + self._category = ServiceCategory.objects.active_translations(get_language(), slug=self.config.category_slug).latest('pk') + queryset = queryset.filter(categories=self._category.pk) + + if not getattr(self.request, 'toolbar', False) or not self.request.toolbar.edit_mode: + queryset = queryset.published() + + return queryset.on_site() + + def render_to_response(self, context, **response_kwargs): + response_kwargs['current_app'] = resolve(self.request.path).namespace + return super(BaseServiceView, self).render_to_response(context, **response_kwargs) + + +class ServiceDetailView(TranslatableSlugMixin, BaseServiceView, DetailView): + model = Service + context_object_name = 'post' + template_name = 'service/service_detail.html' + slug_field = 'slug' + view_url_name = 'service:service-detail' + + def get_context_data(self, **kwargs): + context = super(PostDetailView, self).get_context_data(**kwargs) + context['meta'] = self.get_object().as_meta() + context['use_placeholder'] = get_setting('USE_PLACEHOLDER') + setattr(self.request, SERVICE_CURRENT_SERVICE_IDENTIFIER, self.get_object()) + return context + diff --git a/zsite/static/imgs/phone.png b/zsite/static/imgs/phone.png new file mode 100644 index 0000000..8123c1e Binary files /dev/null and b/zsite/static/imgs/phone.png differ diff --git a/zsite/static/js/app.js b/zsite/static/js/app.js index 303e7b5..c9269de 100644 --- a/zsite/static/js/app.js +++ b/zsite/static/js/app.js @@ -1 +1,3 @@ -console.log('Yep'); \ No newline at end of file +angular.module('zuykov', ['ui.bootstrap']); + +console.log('Yep'); diff --git a/zsite/static/js/forms.js b/zsite/static/js/forms.js new file mode 100644 index 0000000..ae2a85d --- /dev/null +++ b/zsite/static/js/forms.js @@ -0,0 +1,52 @@ + +angular.module('zuykov').controller('ModalFormCtrl', function ($scope, $modal, $log) { + + $scope.items = ['item1', 'item2', 'item3']; + + $scope.animationsEnabled = true; + + + $scope.open = function (form_id) { + console.log('open'); + var modalInstance = $modal.open({ + animation: $scope.animationsEnabled, + templateUrl: 'modalFormTemplate-' + form_id + '.html', + controller: 'ModalFormInstanceCtrl', + resolve: { + items: function () { + return $scope.items; + } + } + }); + + modalInstance.result.then(function (selectedItem) { + $scope.selected = selectedItem; + }, function () { + $log.info('Modal dismissed at: ' + new Date()); + }); + }; + + $scope.toggleAnimation = function () { + $scope.animationsEnabled = !$scope.animationsEnabled; + }; + +}) + +// Please note that $modalInstance represents a modal window (instance) dependency. +// It is not the same as the $modal service used above. +.controller('ModalFormInstanceCtrl', function ($scope, $modalInstance, items, $log, $timeout) { + + $log.info('opened modal'); + $timeout(function () { + $('.forms').djangocms_forms(); + }, 1000); + + + $scope.ok = function () { + $modalInstance.close("params"); + }; + + $scope.cancel = function () { + $modalInstance.dismiss('cancel'); + }; +}); \ No newline at end of file diff --git a/zsite/static/less/about.less b/zsite/static/less/about.less index ce67d1e..118fa9b 100644 --- a/zsite/static/less/about.less +++ b/zsite/static/less/about.less @@ -1,10 +1,24 @@ .about-map { position: absolute; z-index: 0; - width: 65%; - margin-left: 35%; + + margin-top: -16%; + width: 65%; + + @media (min-width: 0) { + display: none; + } + @media (min-width: @screen-md-min) { + display: block; + margin-left: 40%; + } + @media (min-width: @screen-lg-min) { + display: block; + margin-left: 35%; + } + img { width: 100%; } diff --git a/zsite/static/less/consultation.less b/zsite/static/less/consultation.less new file mode 100644 index 0000000..5c2d7a0 --- /dev/null +++ b/zsite/static/less/consultation.less @@ -0,0 +1,40 @@ +.modal-consultation { + img { + float: left; + width: 30%; } + + .forms { + padding-left: 35%; + + h3 { + margin-top: 18px; + + } + + label { + width: 25%; + } + + .help-text { + margin-top: 10px; + margin-bottom: 5px; + } + button { + margin-top: 15px; + } + + .form-success { + margin-top: 20px; + + font-size: 22px; + } + } +} + +.consultation { + .btn-consultation:extend(.btn-default, .btn-xs) {} +} + +.content { + .btn-consultation:extend(.btn-primary) {} +} \ No newline at end of file diff --git a/zsite/static/less/main.less b/zsite/static/less/main.less index bfb62fa..bf93cb6 100644 --- a/zsite/static/less/main.less +++ b/zsite/static/less/main.less @@ -18,6 +18,7 @@ @import 'blog.less'; +@import 'consultation.less'; @import url(http://fonts.googleapis.com/css?family=PT+Sans:400,700,400italic,700italic|Roboto+Slab:700,400&subset=cyrillic-ext,latin); diff --git a/zsite/static/less/people.less b/zsite/static/less/people.less index f78b915..aed5823 100644 --- a/zsite/static/less/people.less +++ b/zsite/static/less/people.less @@ -25,7 +25,7 @@ .people-description { width: 80%; - font-size: 16px; + font-size: 15px; padding-top: 25px; padding-left: 140px; diff --git a/zsite/templates/aldryn_people/plugins/feature/people_list.html b/zsite/templates/aldryn_people/plugins/feature/people_list.html index 5f98dd5..1002063 100644 --- a/zsite/templates/aldryn_people/plugins/feature/people_list.html +++ b/zsite/templates/aldryn_people/plugins/feature/people_list.html @@ -1,4 +1,4 @@ -{% load i18n thumbnail shuffle %} +{% load cms_tags i18n thumbnail shuffle %}
{% if people_groups %} diff --git a/zsite/templates/base.html b/zsite/templates/base.html index d4f9fd0..c8af3ea 100644 --- a/zsite/templates/base.html +++ b/zsite/templates/base.html @@ -1,7 +1,7 @@ -{% load cms_tags staticfiles sekizai_tags menu_tags i18n %} +{% load cms_tags staticfiles sekizai_tags menu_tags i18n %} {% load pipeline %} - + {% block title %}Zuykov and partners{% endblock title %} @@ -14,8 +14,10 @@ {% render_block "css" %} + {% cms_toolbar %}
+ - -

{% trans 'Our Services' %}

-
-
    - {% show_menu 0 1 100 100 "menu.html" %} -
+ + {% show_menu 0 1 100 100 "menu_header.html" %} {% block content %}{% endblock content %}