Commits

girgon committed 43d7eff Merge

Слияние

Comments (0)

Files changed (8)

+os-guild
 
 __Todo__
 
+EPIO deployment explained
+=========================
+Make ep.io account or get an invitation =)
+
+- install epio module ( pip install epio )
+- patch epio to work in windows ( todo )
+- add public key to epio account
+- update solr schema if db is changed: 'manage.py build_solr_schema >solr_schema.xml'
+- add line to the local .hg/hgrc file: 'epio = ssh://vcs@upload.ep.io/os-guild'
+- do 'hg push epio'
+- do [optionaly] 'epio django syncdb'
+- do [optionaly] 'epio django createcachetable cache_table'
+
+Things to watch out:
+Note, epio bundle is read-only directory structure, so avatars and attachements
+uploading must be directed to env-var['EPIO_DATA_DIRECTORY']
+That could be achieved with the proper symlinks in epio.ini file but i didn't
+tried it yet.
+
+Things to-do for epio:
+- [done] avatar and attachements redirection via symlink to writtable directory
+- [done] replace forum search index backend from file-based whoosh to the epio approoved solr
+
+EPIO and static files:
+Because the filesystem your app runs on is read-only by default, if you're trying 
+to use the collectstatic command you'll need to do one of two things:
+
+- Run collectstatic locally, before you upload. 
+- Collect your static files to a directory in the writable area. This isn't advised, 
+as there's a slight performance penalty on serving files from the writable area.
+
 Folder layout
 =============
 

djangobb_forum/settings.py

 from django.conf import settings
 import re
 
+from bundle_config import config
+
 def get(key, default):
     return getattr(settings, key, default)
 
 DEFAULT_MARKUP = get('DJANGOBB_DEFAULT_MARKUP', 'bbcode')
 NOTICE = get('DJANGOBB_NOTICE', '')
 USER_ONLINE_TIMEOUT = get('DJANGOBB_USER_ONLINE_TIMEOUT', 15)
-EMAIL_DEBUG = get('DJANGOBB_FORUM_EMAIL_DEBUG', False)
+EMAIL_DEBUG = get('DJANGOBB_FORUM_EMAIL_DEBUG', True)
 POST_USER_SEARCH = get('DJANGOBB_POST_USER_SEARCH', 1)
 
 # GRAVATAR Extension
+# This is an example epio.ini file.
+# We suggest you edit it to fit your application's needs.
+# Documentation for the options is available at www.ep.io/docs/epioini/
+
+[wsgi]
+
+# Location of your requirements file
+requirements = epio_requirements.txt
+
+
+[static]
+
+# Serve the static directory directly as /static
+# /static = static
+
+
+[services]
+
+postgres = true
+solr = true
+
+# Uncomment to enable the PostgreSQL service.
+# postgres = true
+
+# Uncomment to enable the Redis service
+# redis = true
+
+
+[checkout]
+
+# By default your code is put in a directory called 'app'.
+# You can change that here.
+# directory_name = my_project
+
+
+[env]
+
+# Set any additional environment variables here. For example:
+# IN_PRODUCTION = true
+
+DJANGO_SETTINGS_MODULE = epio_settings
+
+[symlinks]
+
+# Any symlinks you'd like to add. As an example, link the symlink 'config.py'
+# to the real file 'configs/epio.py':
+# config.py = configs/epio.py
+
+static/forum/avatars = ../../app_data
+static/forum/attachments = ../../app_data
+
+
+# #### If you're using Django, you'll want to uncomment some or all of these lines ####
+# [django]
+# # Path to your project root, relative to this directory.
+# base = .       
+#
+# [static]
+# Serve the admin media
+# # Django 1.3
+# /static/admin = ../shortcuts/django-admin-media/
+# # Django 1.2 and below
+# /media = ../shortcuts/django-admin-media/
+#
+# [env]
+# # Use a different settings module for ep.io (i.e. with DEBUG=False)
+# DJANGO_SETTINGS_MODULE = production_settings
+
+[static]
+/static = static/
+/media = ../shortcuts/django-admin-media/
+#/forum/avatars = ../../app_data
+#/forum/attachments = ../../app_data
+
+[solr]
+schema_file = solr_schema.xml 
+memory = 128

epio_requirements.txt

+Django>=1.2
+PIL>=1.1.7
+django-registration==0.7
+django-haystack>=1.1.0
+-e hg+http://bitbucket.org/benoitc/django-authopenid#egg=django-authopenid
+Markdown==2.0
+django-messages==0.4.4
+pysolr>=2.0.13
+# -*- coding: utf-8 -*-
+import os.path
+import sys
+import re
+
+from bundle_config import config
+
+PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+    # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+# ep.io provides!
+#DATABASES = {
+#    'default': {
+#        'ENGINE': 'django.db.backends.postgresql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+#        'NAME': 'oseller',                      # Or path to database file if using sqlite3.
+#        'USER': 'oseller',                      # Not used with sqlite3.
+#        'PASSWORD': 'givemeaccess',                  # Not used with sqlite3.
+#        'HOST': 'mysql.pocketheroes.org',                      # Set to empty string for localhost. Not used with sqlite3.
+#        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+#    }
+#}
+
+# django 1.2 cache settings
+# (note what we need to provide a table in db)
+CACHE_BACKEND = "db://cache_table?timeout=60"
+
+
+# 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.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'Europe/Moscow'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+LANGUAGES = (
+    ('en', 'English'),
+    ('es', 'Spanish'),
+    ('fr', 'France'),
+    ('lt', 'Lithuanian'),
+    ('pl', 'Polish'),
+    ('ru', 'Russian'),
+    ('zh_CN', 'Chinese'),
+    ('de', 'German'),
+    ('vi', 'Vietnamese'),
+    ('it', 'Italian'),
+    ('cs', 'Czech'),
+    ('ca', 'Catalan'),
+)
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'static')
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/static/'
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+
+SECRET_KEY = '(8+3!p7@03hyb&70xn#w8k)pbe&201s4n4*c+un)+7q$n%u&ls'
+# ok, should take from config -> SECRET_KEY = config['core']['secret_key']
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.load_template_source',
+    'django.template.loaders.app_directories.load_template_source',
+#     'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.cache.UpdateCacheMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.middleware.locale.LocaleMiddleware',
+    'django_authopenid.middleware.OpenIDMiddleware',
+    'django.middleware.cache.FetchFromCacheMiddleware',
+    'django.middleware.transaction.TransactionMiddleware',
+    'djangobb_forum.middleware.LastLoginMiddleware',
+    'djangobb_forum.middleware.UsersOnline',
+)
+
+ROOT_URLCONF = 'urls'
+
+TEMPLATE_DIRS = (
+    # 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.
+    os.path.join(PROJECT_ROOT, 'templates'),
+)
+
+INSTALLED_APPS = (
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.sitemaps',
+    'django.contrib.admin',
+    'django.contrib.admindocs',
+    'registration',
+    'django_authopenid',
+    'djangobb_forum',
+    'guildsite',
+    'haystack',
+    'messages',
+)
+
+try:
+    import mailer
+    INSTALLED_APPS += ('mailer',)
+    EMAIL_BACKEND = "mailer.backend.DbBackend"
+except ImportError:
+    pass
+
+try:
+    import south
+    INSTALLED_APPS += ('south',)
+    SOUTH_TESTS_MIGRATE = False
+except ImportError:
+    pass
+
+FORCE_SCRIPT_NAME = ''
+
+TEMPLATE_CONTEXT_PROCESSORS = (
+    'django.core.context_processors.auth',
+    'django.core.context_processors.debug',
+    'django.core.context_processors.i18n',
+    'django.core.context_processors.media',
+    'django.core.context_processors.request',
+    'django_authopenid.context_processors.authopenid',
+    'djangobb_forum.context_processors.forum_settings',
+)
+
+# Haystack settings
+HAYSTACK_SITECONF = 'search_sites'
+HAYSTACK_SEARCH_ENGINE = 'solr'
+HAYSTACK_SOLR_URL = 'http://%s:%s%s' % (config['solr']['host'],config['solr']['port'],config['solr']['path'])
+#HAYSTACK_WHOOSH_PATH = os.path.join(PROJECT_ROOT, 'djangobb_index')
+
+# Account settings
+ACCOUNT_ACTIVATION_DAYS = 10
+LOGIN_REDIRECT_URL = '/forum/'
+LOGIN_URL = '/forum/account/signin/'
+
+#Cache settings
+CACHE_MIDDLEWARE_ANONYMOUS_ONLY = True
+
+#DJANGOBB_AVATARS_UPLOAD_TO = config['core']['data_directory']
+#DJANGOBB_ATTACHMENT_UPLOAD_TO = config['core']['data_directory']
+
+EMAIL_HOST = 'mail.os-guild.ioupg.com'
+EMAIL_HOST_PASSWORD = 'plsonegai'
+EMAIL_HOST_USER = 'admin@os-guild.ioupg.com'
+EMAIL_PORT = '587'
+EMAIL_SUBJECT_PREFIX = '[OS]'
+EMAIL_USE_TLS = True
+#EMAIL_HOST = 'email-smtp.us-east-1.amazonaws.com'
+#EMAIL_HOST_PASSWORD = 'Ah2B6gjHBVAmrIsjcemEVlVkdirjDO6C50y4M9egh1GS'
+#EMAIL_HOST_USER = 'AKIAJUU7LVQJY5TII6DA'
+#EMAIL_PORT = '465'
+#EMAIL_SUBJECT_PREFIX = '[OS]'
+EMAIL_USE_TLS = True
+
+DEFAULT_FROM_EMAIL = 'Admin <sigman@ioupg.com>'
+
+try:
+    from local_settings import *
+except ImportError:
+    pass
+Django>=1.2
+PIL>=1.1.7
+django-registration==0.7
+django-haystack>=1.1.0
+-e hg+http://bitbucket.org/benoitc/django-authopenid#egg=django-authopenid
+Markdown==2.0
+django-messages==0.4.4
+whoosh
+<?xml version="1.0" ?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<schema name="default" version="1.1">
+  <types>
+    <fieldtype name="string"  class="solr.StrField" sortMissingLast="true" omitNorms="true"/>
+    <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>
+
+    <!-- Numeric field types that manipulate the value into
+         a string value that isn't human-readable in its internal form,
+         but with a lexicographic ordering the same as the numeric ordering,
+         so that range queries work correctly. -->
+    <fieldType name="sint" class="solr.SortableIntField" sortMissingLast="true" omitNorms="true"/>
+    <fieldType name="slong" class="solr.SortableLongField" sortMissingLast="true" omitNorms="true"/>
+    <fieldType name="sfloat" class="solr.SortableFloatField" sortMissingLast="true" omitNorms="true"/>
+    <fieldType name="sdouble" class="solr.SortableDoubleField" sortMissingLast="true" omitNorms="true"/>
+
+    <fieldType name="date" class="solr.DateField" sortMissingLast="true" omitNorms="true"/>
+
+    <fieldType name="text" class="solr.TextField" positionIncrementGap="100">
+      <analyzer type="index">
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <!-- in this example, we will only use synonyms at query time
+        <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/>
+        -->
+        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
+        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+<!--        <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/> -->
+		<filter class="solr.SnowballPorterFilterFactory" language="Russian" /> 
+		<!-- <filter class="solr.RussianLightStemFilterFactory" /> -->
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
+      </analyzer>
+      <analyzer type="query">
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+        <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/>
+        <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt"/>
+        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <!-- <filter class="solr.EnglishPorterFilterFactory" protected="protwords.txt"/> -->
+		<filter class="solr.SnowballPorterFilterFactory" language="Russian" /> 
+		<!-- <filter class="solr.RussianLightStemFilterFactory" /> -->
+        <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
+      </analyzer>
+    </fieldType>
+
+    <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">
+      <analyzer>
+        <tokenizer class="solr.WhitespaceTokenizerFactory"/>
+      </analyzer>
+    </fieldType>
+    
+    <fieldType name="ngram" class="solr.TextField" >
+      <analyzer type="index">
+        <tokenizer class="solr.KeywordTokenizerFactory"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+        <filter class="solr.NGramFilterFactory" minGramSize="3" maxGramSize="15" />
+      </analyzer>
+      <analyzer type="query">
+        <tokenizer class="solr.KeywordTokenizerFactory"/>
+        <filter class="solr.LowerCaseFilterFactory"/>
+      </analyzer>
+    </fieldType>
+    
+    <fieldType name="edge_ngram" class="solr.TextField" positionIncrementGap="1">
+      <analyzer type="index">
+        <tokenizer class="solr.WhitespaceTokenizerFactory" />
+        <filter class="solr.LowerCaseFilterFactory" />
+        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+        <filter class="solr.EdgeNGramFilterFactory" minGramSize="2" maxGramSize="15" side="front" />
+      </analyzer>
+      <analyzer type="query">
+        <tokenizer class="solr.WhitespaceTokenizerFactory" />
+        <filter class="solr.LowerCaseFilterFactory" />
+        <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="0" catenateNumbers="0" catenateAll="0" splitOnCaseChange="1"/>
+      </analyzer>
+    </fieldType>
+  </types>
+
+  <fields>   
+    <!-- general -->
+    <field name="id" type="string" indexed="true" stored="true" multiValued="false" required="true"/>
+    <field name="django_ct" type="string" indexed="true" stored="true" multiValued="false" />
+    <field name="django_id" type="string" indexed="true" stored="true" multiValued="false" />
+
+    <dynamicField name="*_i"  type="sint"    indexed="true"  stored="true"/>
+    <dynamicField name="*_s"  type="string"  indexed="true"  stored="true"/>
+    <dynamicField name="*_l"  type="slong"   indexed="true"  stored="true"/>
+    <dynamicField name="*_t"  type="text"    indexed="true"  stored="true"/>
+    <dynamicField name="*_b"  type="boolean" indexed="true"  stored="true"/>
+    <dynamicField name="*_f"  type="sfloat"  indexed="true"  stored="true"/>
+    <dynamicField name="*_d"  type="sdouble" indexed="true"  stored="true"/>
+    <dynamicField name="*_dt" type="date"    indexed="true"  stored="true"/>
+    
+
+    <field name="category" type="text" indexed="true" stored="true" multiValued="false" />
+
+    <field name="forum" type="slong" indexed="true" stored="true" multiValued="false" />
+
+    <field name="created" type="date" indexed="true" stored="true" multiValued="false" />
+
+    <field name="text" type="text" indexed="true" stored="true" multiValued="false" />
+
+    <field name="author" type="text" indexed="true" stored="true" multiValued="false" />
+
+    <field name="topic" type="text" indexed="true" stored="true" multiValued="false" />
+
+  </fields>
+
+  <!-- field to use to determine and enforce document uniqueness. -->
+  <uniqueKey>id</uniqueKey>
+
+  <!-- field for the QueryParser to use when an explicit fieldname is absent -->
+  <defaultSearchField>text</defaultSearchField>
+
+  <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
+  <solrQueryParser defaultOperator="AND" />
+</schema>