commit e036abd313668dcacf00e40140ddcdcd3d5cd753 Author: Lance Edgar Date: Sat Apr 1 15:17:32 2017 -0500 Initial commit as generated from: pcreate -t websauna_app hotcooler https://websauna.org/docs/tutorials/gettingstarted/tutorial_03.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..997db2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Never commit our secrets files +*secrets.ini + +# Default .gitignore with common Pythonic web ignores +venv +build/ +dist/ +*.pyc +__pycache__ +*.egg +*.egg-info + +# Logs +*.log + +# Database dumbs +*.sql +*.sqlite + +# If somebody does npm local installs +node_modules + +# Created by running a celery +celerybeat* + +# pytest-splinter creates screenshots from failed browsers tests. +# let's not pollute source tree with them by accient. +/*.tests.* + +# Static asset cache busting +cache-manifest.json +perma-asset + +# pytest and tox cache +.cache +.tox +.eggs diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..35a34f3 --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,4 @@ +0.0 +--- + +- Initial version diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d2bad49 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,2 @@ +include *.txt *.ini *.cfg *.rst +recursive-include hotcooler *.ico *.png *.css *.gif *.jpg *.pt *.txt *.mak *.mako *.js *.html *.xml *.ini *.yml *.yaml diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..fc13323 --- /dev/null +++ b/README.rst @@ -0,0 +1,57 @@ +This is a Websauna application package for hotcooler. + +To run this package you need Python 3.4+, PostgresSQL and Redis. + +Installation +============ + +This installation method assumes you the author of the hotcooler application and wish to develop it. Below are instructions to to install the package to a Python virtual environment using pip command in an editable mode. + +Example:: + + cd hotcooler # This is the folder with setup.py file + virtualenv venv + source venv/bin/activate + + # Make sure pip itself is up-to-date + pip install -U pip + + # Install the package and its dependencies to a currently + # activated virtualenv from the folder with setup.py file + pip install -e "." + +Running the website +=================== + +Local development machine +------------------------- + +Example (OSX / Homebrew):: + + # Create PostgreSQL database + psql create hotcooler_dev + + # Write table schemas for models + ws-sync-db hotcooler/conf/development.ini + + # Start web server + ws-pserve hotcooler/conf/development.ini --reload + +Running the test suite +====================== + +Example:: + + # Install testing dependencies + pip install ".[dev,test]" + + # Create database used for unit testing + psql create hotcooler_test + + # Run test suite using py.test running + py.test + +More information +================ + +Please see https://websauna.org/ \ No newline at end of file diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..f90ad94 --- /dev/null +++ b/alembic/env.py @@ -0,0 +1,3 @@ +from websauna.system.devop import alembic + +alembic.run_alembic(package="hotcooler") \ No newline at end of file diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..f54981d --- /dev/null +++ b/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + +import datetime +import websauna.system.model.columns +from sqlalchemy.types import Text # Needed from proper creation of JSON fields as Alembic inserts astext_type=Text() row + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/README.txt b/alembic/versions/README.txt new file mode 100644 index 0000000..78e2af7 --- /dev/null +++ b/alembic/versions/README.txt @@ -0,0 +1,3 @@ +This is a placeholder. + +ws-alembic command will place generated scripts here. \ No newline at end of file diff --git a/hotcooler/__init__.py b/hotcooler/__init__.py new file mode 100644 index 0000000..7737f58 --- /dev/null +++ b/hotcooler/__init__.py @@ -0,0 +1,57 @@ +"""App entry point and configuration.""" + +import websauna.system + + +class Initializer(websauna.system.Initializer): + """An initialization configuration used for starting hotcooler. + + Override parent class methods to customize application behavior. + """ + + def configure_static(self): + """Configure static asset serving and cache busting.""" + super(Initializer, self).configure_static() + + self.config.registry.static_asset_policy.add_static_view('hotcooler-static', 'hotcooler:static') + + def configure_templates(self): + """Include our package templates folder in Jinja 2 configuration.""" + super(Initializer, self).configure_templates() + + self.config.add_jinja2_search_path('hotcooler:templates', name='.html', prepend=True) # HTML templates for pages + self.config.add_jinja2_search_path('hotcooler:templates', name='.txt', prepend=True) # Plain text email templates (if any) + self.config.add_jinja2_search_path('hotcooler:templates', name='.xml', prepend=True) # Sitemap and misc XML files (if any) + + def configure_views(self): + """Configure views for your application. + + Let the config scanner to pick ``@simple_route`` definitions from scanned modules. Alternative you can call ``config.add_route()`` and ``config.add_view()`` here. + """ + # We override this method, so that we route home to our home screen, not Websauna default one + from . import views + self.config.scan(views) + + def configure_models(self): + """Register the models of this application.""" + from . import models + self.config.scan(models) + + def configure_model_admins(self): + """Register the models of this application.""" + + # Call parent which registers user and group admins + super(Initializer, self).configure_model_admins() + + # Scan our admins + from . import admins + self.config.scan(admins) + + def run(self): + super(Initializer, self).run() + + +def main(global_config, **settings): + init = Initializer(global_config) + init.run() + return init.make_wsgi_app() diff --git a/hotcooler/admins.py b/hotcooler/admins.py new file mode 100644 index 0000000..66a3656 --- /dev/null +++ b/hotcooler/admins.py @@ -0,0 +1 @@ +"""Place your admin resources in this file.""" \ No newline at end of file diff --git a/hotcooler/conf/base.ini b/hotcooler/conf/base.ini new file mode 100644 index 0000000..a5fcdc0 --- /dev/null +++ b/hotcooler/conf/base.ini @@ -0,0 +1,39 @@ +# Definition of hotcooler name and properties shared among development, testing and production instances + +# +# WSGI entry point and websauna INI settings +# +[app:main] +use = egg:hotcooler +websauna.init = hotcooler.Initializer + +# +# Websauna settings +# + +# Page/email title +websauna.site_name = hotcooler + +# HTML title tag for the browser address bar +websauna.site_title = hotcooler + +# Branding slogan +websauna.site_tag_line = Your site goes here + +websauna.site_url = http://localhost:6543 + +# Your name +websauna.site_author = hotcooler team + +# Used internally with databases/backups/etc. +websauna.site_id = hotcooler + +# pyramid_mailer settings +mail.default_sender = no-reply@example.com +mail.default_sender_name = hotcooler team + + + + + + diff --git a/hotcooler/conf/development.ini b/hotcooler/conf/development.ini new file mode 100644 index 0000000..bc3a884 --- /dev/null +++ b/hotcooler/conf/development.ini @@ -0,0 +1,15 @@ +# pserve and command line configuration for a local development machine + +[includes] +include_ini_files = + resource://websauna/conf/development.ini + resource://hotcooler/conf/base.ini + resource://websauna/conf/base.ini + +[app:main] +websauna.site_id = hotcooler_dev +websauna.site_email_prefix = [hotcooler DEV] +sqlalchemy.url = postgresql://localhost/hotcooler_dev +websauna.secrets_file = resource://hotcooler/conf/development-secrets.ini + + diff --git a/hotcooler/conf/production.ini b/hotcooler/conf/production.ini new file mode 100644 index 0000000..7a11869 --- /dev/null +++ b/hotcooler/conf/production.ini @@ -0,0 +1,16 @@ +# pserve and command line configuration for a production server + +[includes] +include_ini_files = + resource://websauna/conf/production.ini + resource://hotcooler/conf/base.ini + resource://websauna/conf/base.ini + +[app:main] +use = egg:hotcooler +websauna.init = hotcooler.Initializer +websauna.site_id = hotcooler_prod +websauna.site_email_prefix = [hotcooler] +sqlalchemy.url = postgresql://localhost/hotcooler_prod +websauna.secrets_file = resource://hotcooler/conf/production-secrets.ini + diff --git a/hotcooler/conf/staging.ini b/hotcooler/conf/staging.ini new file mode 100644 index 0000000..105b08e --- /dev/null +++ b/hotcooler/conf/staging.ini @@ -0,0 +1,13 @@ +# pserve and command line configuration for a staging server + +[includes] +include_ini_files = + resource://hotcooler/conf/production.ini + resource://websauna/conf/production.ini + resource://hotcooler/conf/base.ini + resource://websauna/conf/base.ini + +[app:main] +websauna.site_id = hotcooler_staging +sqlalchemy.url = postgresql://localhost/hotcooler_staging +websauna.secrets_file = resource://hotcooler/conf/staging-secrets.ini diff --git a/hotcooler/conf/test.ini b/hotcooler/conf/test.ini new file mode 100644 index 0000000..5638410 --- /dev/null +++ b/hotcooler/conf/test.ini @@ -0,0 +1,14 @@ +# py.test --ini configuration for running the hotcooler test suite + +[includes] +include_ini_files = + resource://websauna/conf/test.ini + resource://hotcooler/conf/base.ini + resource://websauna/conf/base.ini + +[app:main] +websauna.site_id = hotcooler_test +websauna.site_email_prefix = [hotcooler TEST] +sqlalchemy.url = postgresql://localhost/hotcooler_test +websauna.secrets_file = resource://hotcooler/conf/test-secrets.ini +websauna.test_web_server_port = 8533 diff --git a/hotcooler/models.py b/hotcooler/models.py new file mode 100644 index 0000000..b84d880 --- /dev/null +++ b/hotcooler/models.py @@ -0,0 +1 @@ +"""Place your SQLAlchemy models in this file.""" \ No newline at end of file diff --git a/hotcooler/static/logo.png b/hotcooler/static/logo.png new file mode 100644 index 0000000..90c1dc2 Binary files /dev/null and b/hotcooler/static/logo.png differ diff --git a/hotcooler/static/theme.css b/hotcooler/static/theme.css new file mode 100644 index 0000000..fd7f7f9 --- /dev/null +++ b/hotcooler/static/theme.css @@ -0,0 +1,4 @@ +/* Don't let the logo image to grow too big */ +.navbar-brand img { + max-height: 26px; +} \ No newline at end of file diff --git a/hotcooler/templates/email/header.html b/hotcooler/templates/email/header.html new file mode 100644 index 0000000..dfdf264 --- /dev/null +++ b/hotcooler/templates/email/header.html @@ -0,0 +1,23 @@ +{# HTML email header + +.. note :: + + If you wish to use the company logo here, so that it actually shows up in the opened email, don't serve it from your own server. Instead upload the logo to Google Drive and hot link it there. GMail and other email applications are little bit picky where they allow images come from. + +For raw ```` link translate the Google Drive sharing link. From:: + + https://drive.google.com/file/d/0B6nei6GpGxGsb1hnWHlBRFJhxxx/view?usp=sharing + +To:: + + https://drive.google.com/uc?id=0B6nei6GpGxGsb1hnWHlBRFJhxxx +#} + + +

+ + + +

+ + diff --git a/hotcooler/templates/hotcooler/home.html b/hotcooler/templates/hotcooler/home.html new file mode 100644 index 0000000..11a9a20 --- /dev/null +++ b/hotcooler/templates/hotcooler/home.html @@ -0,0 +1,24 @@ +{# Template for home view #} + +{% extends "site/base.html" %} + +{% block content %} + +
+

{{ site_name }}

+

+ {{ site_tag_line }} +

+
+ + {% if request.user %} +

+ Welcome {{ request.user.friendly_name }}! +

+ {% else %} +

+ Welcome visitor! +

+ {% endif %} + +{% endblock %} \ No newline at end of file diff --git a/hotcooler/templates/site/css.html b/hotcooler/templates/site/css.html new file mode 100644 index 0000000..35129dc --- /dev/null +++ b/hotcooler/templates/site/css.html @@ -0,0 +1,20 @@ +{# Specify CSS section for in the site #} + +{# Include Bootstrap CSS from Websauna core package - http://getbootstrap.com/ #} + + +{# Include Font-Awesome icons from CDN - http://fontawesome.io/ #} + + +{# Include some default Websauna styles #} + + +{# Include hotcooler package theme #} + + +{# Include CSS for widgets #} +{% if request.on_demand_resource_renderer %} + {% for css_url in request.on_demand_resource_renderer.get_resources("css") %} + + {% endfor %} +{% endif %} \ No newline at end of file diff --git a/hotcooler/templates/site/logo.html b/hotcooler/templates/site/logo.html new file mode 100644 index 0000000..34875b6 --- /dev/null +++ b/hotcooler/templates/site/logo.html @@ -0,0 +1,6 @@ +{# Override the default navigation bar logo from Websauna templates #} + +{# Logo file taken from here https://openclipart.org/detail/1105/workman-ahead-roadsign #} + + hotcooler + \ No newline at end of file diff --git a/hotcooler/tests/__init__.py b/hotcooler/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hotcooler/tests/test_login.py b/hotcooler/tests/test_login.py new file mode 100644 index 0000000..5539583 --- /dev/null +++ b/hotcooler/tests/test_login.py @@ -0,0 +1,47 @@ +"""An example login test case.""" + +import transaction + +from sqlalchemy.orm.session import Session +from splinter.driver import DriverAPI + +from websauna.tests.utils import create_user +from websauna.tests.utils import EMAIL +from websauna.tests.utils import PASSWORD +from websauna.system import Initializer + + +def test_login(web_server:str, browser:DriverAPI, dbsession:Session, init:Initializer): + """Login as a user to the site. + + This is a functional test. Prepare the test by creating one user in the database. Then try to login as this user by using Splinter test browser. + + :param web_server: Functional web server py.test fixture - this string points to a started web server with test.ini configuration. + + :param browser: A Splinter web browser used to execute the tests. By default ``splinter.driver.webdriver.firefox.WebDriver``, but can be altered with py.test command line options for pytest-splinter. + + :param dbsession: Active SQLAlchemy database session for the test run. + + :param init: Websauna Initializer which ramps up the environment with the default ``test.ini`` and exposes the test config. + """ + + with transaction.manager: + # Create a dummy example@example.com user we test + create_user(dbsession, init.config.registry, email=EMAIL, password=PASSWORD) + + # Direct Splinter browser to the website + b = browser + b.visit(web_server) + + # This link should be in the top navigation + b.find_by_css("#nav-sign-in").click() + + # Link gives us the login form + assert b.is_element_present_by_css("#login-form") + + b.fill("username", EMAIL) + b.fill("password", PASSWORD) + b.find_by_name("login_email").click() + + # After login we see a profile link to our profile + assert b.is_element_present_by_css("#nav-logout") \ No newline at end of file diff --git a/hotcooler/views.py b/hotcooler/views.py new file mode 100644 index 0000000..292f8a8 --- /dev/null +++ b/hotcooler/views.py @@ -0,0 +1,9 @@ +from websauna.system.http import Request +from websauna.system.core.route import simple_route + + +# Configure view named home at path / using a template hotcooler/home.html +@simple_route("/", route_name="home", renderer='hotcooler/home.html') +def home(request: Request): + """Render site homepage.""" + return {"project": "hotcooler"} diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..7f2b074 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,26 @@ +[tool:pytest] +addopts = + --strict + -p websauna.tests.fixtures + --splinter-make-screenshot-on-failure=false + --ini=hotcooler/conf/test.ini + hotcooler/tests + +# E501: Line too long +# E128: continuation line under +# E731: http://stackoverflow.com/q/25010167/315168 +pep8ignore = E501 E128 E731 + +# Don't let py.test scan py.files in these folders +norecursedirs = alembic .tox .cache .eggs venv + +# Add some default py.test markers +# Slow marker is for tests taking > 15 seconds to complete. +# Fail marker signals the test is expected to fail. +markers = + slow + fail + +[flake8] +ignore = E128 E731_ +max-line-length = 999 \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f22d689 --- /dev/null +++ b/setup.py @@ -0,0 +1,63 @@ +import os +import sys +from setuptools import setup, find_packages + + +here = os.path.abspath(os.path.dirname(__file__)) +with open(os.path.join(here, 'README.rst')) as f: + README = f.read() +with open(os.path.join(here, 'CHANGES.rst')) as f: + CHANGES = f.read() + + +# trying to run python setup.py install or python setup.py develop +if len(sys.argv) >= 2: + if sys.argv[0] == "setup.py" and sys.argv[1] in ("install", "develop"): + # Otherwise so much stuff would be broken later... + # Namely, namespaced packages clash as pip, setup.py and easy_install handle namespaces differently + raise RuntimeError("It is not possible to install this package with setup.py. Use pip to install this package as instructed in Websauna tutorial.") + + +setup(name='hotcooler', + version='0.0', + description='hotcooler', + long_description=README + '\n\n' + CHANGES, + classifiers=[ + "Programming Language :: Python", + "Framework :: Pyramid", + "Topic :: Internet :: WWW/HTTP", + "Topic :: Internet :: WWW/HTTP :: WSGI :: Application", + ], + author='', + author_email='', + url='', + keywords='web websauna pyramid', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + test_suite='hotcooler', + install_requires=['websauna'], + extras_require={ + # Dependencies for running test suite + 'test': [ + "pytest", + "pytest-runner", + "pytest-splinter", + "webtest", + + # Wait until Marionette matures + # http://stackoverflow.com/questions/37761668/cant-open-browser-with-selenium-after-firefox-update + "selenium==2.53.6", + ], + + + # Dependencies to make releases + 'dev': ['websauna[dev]'], + }, + + # Define where this application starts as referred by WSGI web servers + entry_points="""\ + [paste.app_factory] + main = hotcooler:main + """, + )