integrate with django templates (#6735)

This commit is contained in:
Frédéric Péters 2017-03-05 20:22:34 +01:00
parent 0482cdcd7d
commit 01b280aa2c
16 changed files with 360 additions and 21 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0"?>
<theme name="django" version="1.0">
<label>Django</label>
<desc>Test theme for Django port</desc>
<author>Frederic Peters</author>
</theme>

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="{{ site_lang }}">
<head>
<title>{% block page-title %}{{ page_title }}{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{{ css }}"/>
{{ script|safe }}
{% block extrascripts %}
{% endblock %}
</head>
<body>
<div {% if onload %}onload="{{ onload }}"{% endif %}>
<div id="page">
<div id="top">
{% block header %}
<h1>WIP/DJANGO - {% if title %}{{ title }}{% else %}{{ site_name }}{% endif %}</h1>
{% endblock %}
</div>
<div id="main-content">
{% block content %}
{{ prelude }}
{% if breadcrumb %}
<p id="breadcrumb">{{ breadcrumb|safe }}</p>
{% endif %}
{% block body %}
{{ body|safe }}
{% endblock %}
{% endblock %}
</div>
<div id="footer">{{ footer }}</div>
</body>
</html>

View File

@ -0,0 +1,20 @@
{% extends "base.html"%}
{% block body %}
<div>
<h2>HELLO WORLD</h2>
{% regroup forms by category as category_list %}
{% for category in category_list %}
{% if category.grouper %}<h3>{{ category.grouper }}</h3>{% endif %}
<ul>
{% for form in category.list %}
<li><a href="{{ form.url }}">{{ form.title }}</a></li>
{% endfor %}
</ul>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,12 @@
@import url(../../qo/css/sofresh.css);
#page {
-webkit-transform: rotate(2deg);
-webkit-transition: all 200ms ease-out;
-webkit-filter: grayscale(100%);
}
#page:hover {
-webkit-transform: rotate(0deg);
-webkit-filter: none;
}

View File

@ -198,6 +198,7 @@ HOBO_JSON = {
'foobar': 'http://example.net',
'email_signature': 'Hello world.',
'default_from_email': 'noreply@example.net',
'theme': 'clapotis-les-canards',
},
'users': [
{
@ -248,7 +249,7 @@ def test_update_configuration():
assert pub.cfg['misc']['sitename'] == 'Test wcs'
assert pub.cfg['emails']['footer'] == 'Hello world.'
assert pub.cfg['emails']['from'] == 'noreply@example.net'
assert pub.cfg['branding']['theme'] == 'publik'
assert pub.cfg['branding']['theme'] == 'publik-base'
def test_update_profile():
profile = HOBO_JSON.get('profile')

View File

@ -22,3 +22,5 @@ import qommon
import qommon.form
sys.modules['form'] = qommon.form
import compat

View File

@ -18,13 +18,17 @@ import ConfigParser
import os
from threading import Lock
from contextlib import contextmanager
from quixote import get_publisher
from quixote import get_publisher, get_request
from quixote.errors import PublishError
from quixote.http_request import Upload
from django.http import HttpResponse
from django.conf import settings
from django.template import loader, RequestContext, TemplateDoesNotExist
from django.template.response import TemplateResponse
from django.views.generic.base import TemplateView
from .qommon import template
from .qommon.publisher import get_cfg, set_publisher_class
@ -34,7 +38,7 @@ from .qommon.http_response import HTTPResponse
def init_publisher_if_needed():
if get_publisher() is not None:
return
return get_publisher()
# initialize publisher in first request
config = ConfigParser.ConfigParser()
if settings.WCS_LEGACY_CONFIG_FILE:
@ -45,6 +49,31 @@ def init_publisher_if_needed():
for i, extra in enumerate(settings.WCS_EXTRA_MODULES):
config.set('extra', 'cmd_line_extra_%d' % i, extra)
CompatWcsPublisher.configure(config)
return CompatWcsPublisher.create_publisher()
class TemplateWithFallbackView(TemplateView):
quixote_response = None
def get(self, request, *args, **kwargs):
try:
loader.get_template(self.template_name)
except TemplateDoesNotExist:
return quixote(self.request)
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
def render_to_response(self, context, **response_kwargs):
django_response = super(TemplateWithFallbackView, self).render_to_response(context, **response_kwargs)
if self.quixote_response and self.quixote_response.status_code != '200':
django_response.status_code = self.quixote_response.status_code
django_response.reason_phrase = self.quixote_response.reason_phrase
for name, value in self.quixote_response.generate_headers():
if name == 'Content-Length':
continue
django_response[name] = value
return django_response
class CompatHTTPRequest(HTTPRequest):
@ -98,7 +127,33 @@ class CompatWcsPublisher(WcsPublisher):
return output
if not hasattr(response, 'filter') or not response.filter:
return output
return self.render_response(output)
if request.META.get('HTTP_X_POPUP') == 'true':
return '<div class="popup-content">%s</div>' % output
if response.filter and response.filter.get('admin_ezt'):
return self.render_response(output)
current_theme = get_cfg('branding', {}).get('theme', 'default')
theme_directory = template.get_theme_directory(current_theme)
if not os.path.exists(os.path.join(theme_directory, 'templates')):
return self.render_response(output)
if not os.path.exists(os.path.join(theme_directory, 'templates/wcs/base.html')):
return self.render_response(output)
template_name = 'wcs/base.html'
vars = template.get_decorate_vars(output, response)
context = RequestContext(request, vars)
django_response = TemplateResponse(request,
template_name,
context,
content_type=response.content_type,
status=response.status_code)
return django_response
def set_app_dir(self, request):
super(CompatWcsPublisher, self).set_app_dir(request)
settings.THEME_SKELETON_URL = self.get_site_option('theme_skeleton_url')
def process_request(self, request):
self._set_request(request)
@ -113,8 +168,12 @@ class CompatWcsPublisher(WcsPublisher):
output = self.filter_output(request, output)
content = output
django_response = HttpResponse(content,
if isinstance(output, TemplateResponse):
django_response = output
django_response.render()
else:
content = output
django_response = HttpResponse(content,
content_type=response.content_type,
status=response.status_code,
reason=response.reason_phrase)
@ -138,4 +197,36 @@ def quixote(request):
compat_request = CompatHTTPRequest(request)
return pub.process_request(compat_request)
@contextmanager
def request(request):
pub = get_publisher()
compat_request = CompatHTTPRequest(request)
pub.init_publish(compat_request)
pub._set_request(compat_request)
compat_request.process_inputs()
yield
pub._clear_request()
class PublishErrorMiddleware(object):
def process_exception(self, request, exception):
if not isinstance(exception, PublishError):
return None
request = get_request()
exception_body = exception.render()
django_response = HttpResponse(exception_body,
content_type=request.response.content_type,
status=request.response.status_code,
reason=request.response.reason_phrase)
for name, value in request.response.generate_headers():
if name == 'Content-Length':
continue
django_response[name] = value
return django_response
set_publisher_class(CompatWcsPublisher)

View File

@ -32,10 +32,20 @@ from qommon.storage import atomic_write
from wcs.admin.settings import UserFieldsFormDef
from wcs.fields import StringField, EmailField
# TODO: import this from django settings
THEMES_DIRECTORY = os.environ.get('THEMES_DIRECTORY', '/usr/share/publik/themes')
class NoChange(Exception):
pass
def atomic_symlink(src, dst):
if os.path.exists(dst) and os.readlink(dst) == src:
return
if os.path.exists(dst + '.tmp'):
os.unlink(dst + '.tmp')
os.symlink(src, dst + '.tmp')
os.rename(dst + '.tmp', dst)
class CmdCheckHobos(Command):
name = 'hobo_deploy'
@ -146,8 +156,13 @@ class CmdCheckHobos(Command):
if not pub.cfg.get('emails'):
pub.cfg['emails'] = {}
if not pub.cfg.get('branding'):
pub.cfg['branding'] = {'theme': 'publik'}
theme_id = self.all_services.get('variables', {}).get('theme')
if theme_id:
pub.cfg['branding'] = {'theme': 'publik-base'}
tenant_dir = pub.app_dir
theme_dir = os.path.join(tenant_dir, 'theme')
target_dir = os.path.join(THEMES_DIRECTORY, 'publik-base')
atomic_symlink(target_dir, theme_dir)
variables = self.all_services.get('variables') or {}
variables.update(service.get('variables') or {})

View File

@ -1476,6 +1476,67 @@ class RootDirectory(AccessControlled, Directory):
from wcs.api import ApiFormdefsDirectory
return ApiFormdefsDirectory(self.category)._q_index()
def get_context(self):
from wcs.api import is_url_signed, get_user_from_api_query_string
user = get_user_from_api_query_string() or get_request().user
list_all_forms = (user and user.is_admin) or (is_url_signed() and user is None)
list_forms = []
if self.category:
formdefs = FormDef.select(lambda x: (
str(x.category_id) == str(self.category.id) and (
not x.is_disabled() or x.disabled_redirection)),
order_by = 'name')
else:
formdefs = FormDef.select(lambda x: not x.is_disabled() or x.disabled_redirection,
order_by='name',
ignore_errors=True)
charset = get_publisher().site_charset
for formdef in formdefs:
authentication_required = False
if formdef.roles and not list_all_forms:
if not user:
if not formdef.always_advertise:
continue
authentication_required = True
elif logged_users_role().id not in formdef.roles:
for q in user.roles or []:
if q in formdef.roles:
break
else:
if not formdef.always_advertise:
continue
authentication_required = True
formdict = {'title': unicode(formdef.name, charset),
'slug': formdef.url_name,
'url': formdef.get_url(),
'authentication_required': authentication_required}
formdict['redirection'] = bool(formdef.is_disabled() and
formdef.disabled_redirection)
# we include the count of submitted forms so it's possible to sort
# them by popularity
formdict['count'] = formdef.data_class().count()
if formdef.category:
formdict['category'] = unicode(formdef.category.name, charset)
formdict['category_position'] = (formdef.category.position or 0)
else:
formdict['category_position'] = sys.maxint
list_forms.append(formdict)
list_forms.sort(lambda x, y: cmp(x['category_position'], y['category_position']))
for formdict in list_forms:
del formdict['category_position']
return list_forms
def get_categories(self, user):
result = []
formdefs = FormDef.select(

View File

@ -253,10 +253,10 @@ class WcsPublisher(StubWcsPublisher):
z.close()
return results
def try_publish(self, request):
def init_publish(self, request):
if request.get_header('X_WCS_IFRAME_MODE', '') in ('true', 'yes'):
request.response.iframe_mode = True
return QommonPublisher.try_publish(self, request)
return QommonPublisher.init_publish(self, request)
def get_object_visitors(self, object_key):
session_manager = self.session_manager_class()

View File

@ -540,12 +540,12 @@ class QommonPublisher(Publisher, object):
return True
return False
def try_publish(self, request):
def init_publish(self, request):
self.substitutions.reset()
try:
self.set_app_dir(request)
except ImmediateRedirectException, e:
self._set_request(request)
return redirect(e.location)
from vendor import pystatsd
@ -642,6 +642,9 @@ class QommonPublisher(Publisher, object):
self.substitutions.feed(request)
for extra_source in self.extra_sources:
self.substitutions.feed(extra_source(self, request))
def try_publish(self, request):
self.init_publish(request)
return Publisher.try_publish(self, request)
def get_site_language(self):

View File

@ -289,6 +289,9 @@ def get_decorate_vars(body, response, generate_breadcrumb=True):
body = str(body)
if get_request().get_header('x-popup') == 'true':
return {}
kwargs = {}
for k, v in response.filter.items():
if v:
@ -410,7 +413,7 @@ def decorate(body, response):
current_theme = get_cfg('branding', {}).get('theme', 'default')
if response.page_template_key or not template_ezt:
# the theme can provide a default template
possible_filenames = ['template.py']
possible_filenames = []
if response.page_template_key:
possible_filenames.append('template.%s.%s.ezt' % (
get_publisher().APP_NAME, response.page_template_key))
@ -429,12 +432,7 @@ def decorate(body, response):
for dname in possible_dirnames:
filename = os.path.join(dname, fname)
if os.path.exists(filename):
if fname == 'template.py':
template_ezt = get_template_from_script(filename)
if template_ezt is None:
continue
else:
template_ezt = file(filename).read()
template_ezt = file(filename).read()
break
else:
continue

View File

@ -68,7 +68,6 @@ STATIC_URL = '/static/'
# Additional locations of static files
STATICFILES_DIRS = (
os.path.join(PROJECT_PATH, 'wcs', 'static'),
)
# List of finder classes that know how to find static files in
@ -84,6 +83,7 @@ SECRET_KEY = 'k16cal%1fnochq4xbxqgdns-21lt9lxeof5*%j(0ief3=db32&'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'wcs.utils.TemplateLoader',
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
@ -98,6 +98,7 @@ MIDDLEWARE_CLASSES = (
#'django.contrib.messages.middleware.MessageMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'wcs.compat.PublishErrorMiddleware',
)
ROOT_URLCONF = 'wcs.urls'
@ -109,6 +110,16 @@ TEMPLATE_DIRS = (
os.path.join(PROJECT_PATH, 'wcs', 'templates'),
)
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages",
)
INSTALLED_APPS = (
#'django.contrib.auth',
#'django.contrib.contenttypes',
@ -117,6 +128,7 @@ INSTALLED_APPS = (
#'django.contrib.messages',
#'django.contrib.staticfiles',
#'django.contrib.admin',
'gadjo',
)
WCS_LEGACY_CONFIG_FILE = None

View File

@ -14,8 +14,12 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from django.conf import settings
from django.conf.urls import patterns, url
urlpatterns = patterns('',
url(r'', 'wcs.compat.quixote', name='quixote'),
url(r'^$', 'wcs.views.home', name='home'),
)
# other URLs are handled by the quixote handler
urlpatterns += patterns('', url(r'', 'wcs.compat.quixote', name='quixote'))

54
wcs/utils.py Normal file
View File

@ -0,0 +1,54 @@
# w.c.s. - web application for online forms
# Copyright (C) 2005-2013 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import os
import django.template.loaders.filesystem
from django.conf import settings
from .qommon.template import get_theme_directory
from .qommon.publisher import get_cfg, get_publisher
class TemplateLoader(django.template.loaders.filesystem.Loader):
def get_template_sources(self, template_name, template_dirs=None):
if not template_dirs:
template_dirs = []
# theme set by hobo
theme = get_publisher().get_site_option('theme', 'variables')
# templates from tenant directory
if theme:
template_dirs.append(os.path.join(get_publisher().app_dir,
'templates', 'variants', theme))
template_dirs.append(os.path.join(get_publisher().app_dir, 'theme', 'templates'))
template_dirs.append(os.path.join(get_publisher().app_dir, 'templates'))
current_theme = get_cfg('branding', {}).get('theme', 'default')
theme_directory = get_theme_directory(current_theme)
if theme_directory:
# templates from theme directory
theme_directory = os.path.join(theme_directory, 'templates')
if theme:
# template theme set by hobo
template_dirs.append(os.path.join(theme_directory, 'variants', theme))
template_dirs.append(theme_directory)
template_dirs = tuple(template_dirs) + settings.TEMPLATE_DIRS
return super(TemplateLoader, self).get_template_sources(template_name, template_dirs)

View File

@ -14,3 +14,29 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from quixote import get_request
from .forms.root import RootDirectory as FormsRootDirectory
from .qommon.admin.texts import TextsDirectory
from . import compat
class Home(compat.TemplateWithFallbackView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super(Home, self).get_context_data(**kwargs)
with compat.request(self.request):
user = get_request().user
if user:
context['message'] = TextsDirectory.get_html_text('welcome-logged')
else:
context['message'] = TextsDirectory.get_html_text('welcome-unlogged')
root_directory = FormsRootDirectory()
context['forms'] = root_directory.get_context()
return context
home = Home.as_view()