DjangoFloor

Introduction

DjangoFloor is an thin overlay of the Django Python web framework for quickly building websites that are ready to deploy. Its main features are:

  • easy to develop: a single command line generates a fully-working base project (with complete templates), that you can modify step-by-step, with dedicated development settings,
  • easy to deploy: ready to be packaged with separated simple config (.ini) files, without requiring to maintain duplicated config files (prod and dev),
  • allowing offline computation (computation in separated processes or dedicated machines) and two-way communication between the server and the JavaScript world via websockets.
  • with a complete deployment documentation.

Of course, any thing provided by DjangoFloor can be overriden (like the default templates that are based on the well-known Bootstrap 3).

Requirements

DjangoFloor has a bit more requirements than Django:

  • Python 3.4+ (however Django 2.0 also requires Python 3.4+),
  • Django 1.11+,
  • Gunicorn as application server,
  • Redis for caching, sessions, websocket management and celery broker,
  • a reverse proxy like nginx or apache.

DjangoFloor in a nutshell

I assume that you already have a new virtual environment.

pip install djangofloor
djangofloor-createproject
| Your new project name [MyProject]
| Python package name [myproject]
| Initial version [0.1]
| Root project path [.] test
| Use background tasks or websockets [n]
cd test
python setup.py develop
myproject-ctl collectstatic --noinput
myproject-ctl migrate
myproject-ctl check
myproject-ctl runserver

And open your browser on http://localhost:9000/ :)

You can easily create an admin user (password: “admin”) and a standard user (password: “user”):

cat << EOF | myproject-ctl shell
from django.contrib.auth.models import User
if User.objects.filter(username='admin').count() == 0:
    u = User(username='admin')
    u.is_superuser = True
    u.is_staff = True
    u.set_password('admin')
    u.save()
if User.objects.filter(username='user').count() == 0:
    u = User(username='user')
    u.set_password('user')
    u.save()
EOF

Overview

Installing

There are several ways to install DjangoFloor.

Installing from pip

pip install djangofloor

Installing from source

If you prefer install directly from the source:

git clone https://github.com/d9pouces/django-floor.git DjangoFloor
cd DjangoFloor
python setup.py install

Dependencies

Of course, DjangoFloor is not a standalone library and requires several packages:

  • aiohttp-wsgi,
  • django >= 1.11,
  • celery,
  • django-bootstrap3,
  • redis,
  • gunicorn,
  • pip,
  • aiohttp,
  • asyncio_redis.

Several other dependencies are not mandatory but are really useful:

  • django-pipeline,
  • django-debug-toolbar,
  • django-redis-sessions,
  • django-redis,
  • psutil.

You can install these optional dependencies:

pip install djangofloor[extra]

Virtualenvs

You really should consider using virtualenvs, allowing to create several isolated Python environments (each virtualenv having its own set of libraries). virtualenvwrapper may help you to create and manage your virtualenvs.

Creating a new project

Expected archicture

By default, DjangoFloor assumes several architectural choices:

  • your gunicorn server is behind a reverse proxy (nginx or apache),
  • offline computation and websockets messages (if present) are processed by Celery queues,
  • Redis is used as Celery broker, session store and cache database.

Preparing the environment

  • install redis (optional if you do not use offline computations nor websockets)
  • create a virtualenv dedicated to your project
pip install virtualenv
cd myproject
virtualenv venv -p `which python3.5`  # also works with any Python 3.4+
source venv/bin/activate
  • install DjangoFloor:
pip install djangofloor
  • create the base of your project
djangofloor-createproject
| Your new project name [MyProject]
| Python package name [myproject]
| Initial version [0.1]
| Root project path [.] .
  • install scripts in your path. The default setup.py installs a single script (myproject-ctl) that allows you to

access to all Django or Celery commands or launch the aiohttp server.

python setup.py develop
  • prepare the database and collect static files
myproject-ctl collectstatic --noinput
myproject-ctl migrate
  • Now, you just have to run the following two processes (so you need two terminal windows):
myproject-ctl runserver  # the dev. server provided by Django, you should use only `server` in production
myproject-ctl worker

Project structure

The structure of this project closely follows the Django standard one. DjangoFloor only provides default code or values for some parts, so you do not have to write them (but you can override them if you want):

  • instead of writing a complete myproject.settings module, you only have to override some values in a myproject.defaults module,
  • a valid ROOT_URLCONF is provided (with admin views as well as static and media files), you can only add some views in a list in myproject.url.urlpatterns,
  • a default template and a default index view, based on Bootstrap 3,
  • a minimal mapping for some settings in a configuration file,
  • multiple WSGI apps (for Gunicorn and aiohttp) are also provided.

Deploying your project

If your project is uploaded to Pypi:

pip install myproject --upgrade

Of course, you can deploy it in a virtualenv. The configuration of your deployment should be in .ini-like files. The list of configuration files, as well as default values, are shown with the following command line:

myproject-ctl config ini -v 2

After the configuration, you can migrate the database and deploy the static files (CSS or JS):

myproject-ctl collectstatic --noinput
myproject-ctl migrate

Running the servers (in two different processes):

myproject-ctl runserver  # for dev
myproject-ctl server  # for prod
myproject-ctl worker  # for the Celery worker that process offline computation and websocket messages

Development commands

Django already uses a DEBUG variable; DjangoFloor adds a boolean DEVELOPMENT variable that allows to hide commands from the command line. The goal is to hide from the final user commands that are not required in production.

Development files

DjangoFloor can create a Sphinx documentation for your project:

  • configuration file for generating the doc source,
myproject-ctl gen_dev_files . -v 2  --dry

(remove the –dry argument for actually writing files) You can now install sphinx and generate the doc:

pip install sphinx  # some extra packages may be required
cd doc
make html
cd ..

How files are generated?

The gen_dev_files command looks for files in some directories. It use By default, it searches in “djangofloor:djangofloor/dev” and “myproject:myproject/dev”. It means that it looks for files in:

  • [installation_path_of_djangofloor]/djangofloor/static/djangofloor/dev,
  • [installation_path_of_djangofloor]/djangofloor/templates/djangofloor/dev,
  • [installation_path_of_myproject]/myproject/static/myproject/dev,
  • [installation_path_of_myproject]/myproject/templates/myproject/dev.

When files have the same relative path, the last one override the previous ones.

If an original filename ends with “_inc”, then this file will be ignored. If an original filename ends with “_tpl”, then this suffix is silently stripped for building the destination filename. This allows to avoid template files with the “.py” suffix (that can lead to some problems with scripts that import all Python files in a folder).

For example, if we have:

$ ls -lR [installation_path_of_djangofloor]/djangofloor/static/djangofloor/dev
subfolder/test1.txt
subfolder/test2.txt
subfolder/test4.txt_inc
demo.txt
$ ls -lR [installation_path_of_djangofloor]/djangofloor/templates/djangofloor/dev
subfolder/test1.txt
demo.txt
$ ls -lR [installation_path_of_myproject]/myproject/static/myproject/dev
subfolder/test1.txt
demo.txt
$ ls -lR [installation_path_of_myproject]/myproject/templates/myproject/dev
subfolder/test1.txt_tpl
subfolder/test3.txt
demo.txt

Then the gen_dev_files destination/folder command will write the following files:

$ls -lR destination/folder
destination/folder/subfolder/test1.txt
destination/folder/subfolder/test2.txt
destination/folder/subfolder/test3.txt
destination/folder/demo.txt

If the original file is found in a static folder, then it is copied as-is. If it is found in a templates folder, then it is templated before being written.

Template values are:

  • all Django settings,
  • “year”: the current year,
  • “python_version”: current Python version,
  • “use_python3”: True or False,
  • “settings_merger”: the current djangofloor.conf.merger.SettingMerger,
  • “settings_ini” : a .ini representation of the settings.

If the final file is empty, then it is not written.

Due to the search pattern, you can create your own templates that extends DjangoFloor ones.

Using PyCharm

PyCharm Pro can handle Django projects. However, it requires a complete settings file to work. DjangoFloor can luckily generate the required file:

myproject-ctl config python > pycharm_settings.py

Since this file is easily created, you do not have to commit this file to your version control system.

Settings system

By default, Django uses a single Python file for all settings. However, these settings could be organized into three categories:

  • settings that are very common and that can be kept as-is for most projects (USE_TZ = True or MEDIA_URL = ‘/media/’),
  • settings that are specific to your project but common to all instances of your project (like INSTALLED_APPS),
  • settings that are installation-dependent (DATABASE_PASSWORD, …)

You usually have to maintain at least two versions of the same settings file (dev and prod, or one that is versionned and the other one for prod), with the risk of desynchronized files.

On the contrary, DjangoFloor allows to dynamically merge several files to define your settings:

  • djangofloor.conf.defaults that aims at providing good default values,
  • yourproject.defaults for your project-specific settings,
  • /etc/yourproject/settings.py for installation-dependent settings.

You can define a list of settings that are read from a traditionnal text configuration file (.ini format). DjangoFloor also searches for local_settings.py and local_settings.ini setting files in the working directory.

Defining settings

  • many settings are defined in djangofloor.conf.defaults,
  • define your project-wide settings in <project_root>/yourproject/defaults.py (only define overriden settings),
  • translation from .ini config files to Python settings is defined by a list named INI_MAPPING in <project_root>/yourproject/iniconf.py,
  • development settings (like DEBUG = True) can be defined in <project_root>/local_settings.py,

Your defaults.py has the same structure than a traditionnal Django settings.py file (but with less variables ^^ ) INI_MAPPING is a list of djangofloor.conf.config_values.ConfigValue (or of its subclasses, some of them defined in the same module). Several lists are already defined in djangofloor.conf.mapping.

Smarter settings

With DjangoFloor, settings can be built at runtime from some other settings. For example, imagine that you have the following settings in your Django project:

LOG_DIRECTORY = '/var/myproject/logs'
STATIC_ROOT = '/var/myproject/static'
MEDIA_ROOT = '/var/myproject/media'
FILE_UPLOAD_TEMP_DIR = '/var/myproject/tmp'

Modifying the base directory /var/myproject implies that you have four variables to change (and you will forget to change at least one). With DjangoFloor, things are simpler:

LOCAL_PATH = '/var/myproject'
LOG_DIRECTORY = '{LOCAL_PATH}/logs'
STATIC_ROOT = '{LOCAL_PATH}/static'
MEDIA_ROOT = '{LOCAL_PATH}/media'
FILE_UPLOAD_TEMP_DIR = '{LOCAL_PATH}/tmp'

You only have to redefine LOCAL_PATH! You can even go further:

from djangofloor.conf.config_values import Directory
LOCAL_PATH = Directory('/var/myproject')
LOG_DIRECTORY = Directory('{LOCAL_PATH}/logs')
STATIC_ROOT = Directory('{LOCAL_PATH}/static')
MEDIA_ROOT = Directory('{LOCAL_PATH}/media')
FILE_UPLOAD_TEMP_DIR = Directory('{LOCAL_PATH}/tmp')

If you run the check command, you will be warned for missing directories, and the collectstatic and migrate commands will attempt to create them. Of course, you still have settings.MEDIA_ROOT == ‘/var/myproject/media’ in your code, when settings are loaded.

You can use more complex things, instead of having:

SERVER_BASE_URL = 'http://www.example.com'
SERVER_NAME = 'www.example.com'
USE_SSL = False
ALLOWED_HOSTS = ['www.example.com']
CSRF_COOKIE_DOMAIN = 'www.example.com'
EMAIL_SUBJECT_PREFIX = '[www.example.com]'

You could just have:

from djangofloor.conf.config_values import CallableSetting
SERVER_BASE_URL = 'http://www.example.com'
SERVER_NAME = CallableSetting(lambda x: urlparse(x['SERVER_BASE_URL']).hostname, 'SERVER_BASE_URL')
USE_SSL = CallableSetting(lambda x: urlparse(x['SERVER_BASE_URL']).scheme == 'https', 'SERVER_BASE_URL')
ALLOWED_HOSTS = CallableSetting(lambda x: [urlparse(x['SERVER_BASE_URL']).hostname], 'SERVER_BASE_URL')
CSRF_COOKIE_DOMAIN = CallableSetting(lambda x: urlparse(x['SERVER_BASE_URL']).hostname, 'SERVER_BASE_URL')
EMAIL_SUBJECT_PREFIX = CallableSetting(lambda x: '[%s]' % urlparse(x['SERVER_BASE_URL']).hostname, 'SERVER_BASE_URL')

Default settings

Please take a look at djangofloor.conf.defaults for all default settings.

Displaying settings

The complete list of used config files can be displayed using the following command:

yourproject-ctl config python | less (or python yourproject-django.py config python -v 2)
# --------------------------------------------------------------------------------
# Djangofloor version 1.0.22
# Configuration providers:
# --------------------------------------------------------------------------------
...
DF_TEMPLATE_CONTEXT_PROCESSORS = ['updoc.context_processors.most_checked']

You can use the -v 2 flag for a more verbose output:

yourproject-ctl config python -v 2 | less (or python yourproject-django.py config python -v 2)
# --------------------------------------------------------------------------------
# Djangofloor version 1.0.4
# Configuration providers:
#  - Python module "djangofloor.conf.defaults"
#  - Python module "yourproject.defaults"
#  - .ini file "/home/user/.virtualenvs/yourproject/etc/yourproject/settings.ini"
#  - Python file "/home/user/.virtualenvs/yourproject/etc/yourproject/settings.py"
#  - .ini file "/home/user/.virtualenvs/yourproject/etc/yourproject/django.ini"
#  - Python file "/home/user/.virtualenvs/yourproject/etc/yourproject/django.py"
#  - .ini file "./local_settings.ini"
#  - Python file "./local_settings.py"
# --------------------------------------------------------------------------------
...
DF_TEMPLATE_CONTEXT_PROCESSORS = ['updoc.context_processors.most_checked']
#   djangofloor.conf.defaults -> []
#   updoc.defaults -> ['updoc.context_processors.most_checked']

You can also display the corresponding .ini files:

yourproject-ctl config ini -v 2 | less
#  - .ini file "/home/usr/.virtualenvs/easydjango35/etc/easydemo/settings.ini"
#  - .ini file "/home/usr/.virtualenvs/easydjango35/etc/easydemo/django.ini"
#  - .ini file "/home/usr/Developer/Github/EasyDjango/EasyDemo/local_settings.ini"
[global]
admin_email = admin@localhost
      # e-mail address for receiving logged errors
data = django_data
      # where all data will be stored (static/uploaded/temporary files, …) If you change it, you must run the collectstatic and migrate commands again.
language_code = fr_FR
      # default to fr_FR
listen_address = localhost:9000
      # address used by your web server.
secret_key = *secret_key*
server_url = http://localhost:9000/
      # Public URL of your website.
      # Default to "http://listen_address" but should be ifferent if you use a reverse proxy like Apache or Nginx. Example: http://www.example.org.
time_zone = Europe/Paris
      # default to Europe/Paris
log_remote_url =
      # Send logs to a syslog or systemd log daemon.
      # Examples: syslog+tcp://localhost:514/user, syslog:///local7, syslog:///dev/log/daemon, logd:///project_name
...

Websockets and signals

DjangoFloor allows you to define signals in both server (Python) and client (JavaScript) sides. A signal is just a name (a string) with some code related to it. When a signal is triggered by its name, the code is called somewhere, maybe in another processes. You can attach multiple JavaScript or Python functions to the same signal name, and you can call it from the server side as well as from the JS side.

Called signals can be processed by the Python side (“SERVER”) in a Celery worker, or by one or more client browsers.

All signal-related functions require window_info as first argument: this object carries some information to identify the browser window. It should be forwarded as-is when a signal calls another signal. You can also use a django.http.request.HttpRequest, or just None if you do not have to identify a specific browser window.

Architecture

In the following graph, the JS side means any JS signal. For obvious security reasons, a browser can only trigger signals on itself or on the server, not on other browser windows.

Here is the complete scenario:

  • your browser open http://localhost:9000/ ,
  • a Django view receives a django.http.request.HttpRequest (with a unique ID attached to it) and returns a django.http.request.HttpResponse,
  • your browser receives a HTML page as well as some JS and CSS files linked in the HTML page, and this HTML page contains the unique ID,
  • a websocket is open by the JS code and identifies itself with this unique ID, creating a bidirectionnal communication channel,

Python Signal calls are translated into Celery tasks, whatever they are received by the websocket connection (thus triggered by the browser), triggered by the Django view processing a django.http.request.HttpResponse, or triggered by a Celery worker (that is processing a previous signal). Signals that are sent to the browser from Python are written to a Redis queue read by the server that processes the websocket connection.

Defining Python signals

The Python code corresponding to a signal must be a function taking window_info as first argument, prefixed by the djangofloor.decorators.signal decorator. Of course, you can attach multiple functions to the same signal. All codes will be run.

from djangofloor.decorators import signal, everyone
@signal(is_allowed_to=everyone, path='demo.signal', queue='slow')
def slow_signal(window_info, content=''):
    pass
    # [perform a (clever) thing]
In this example:
  • path is the name of the signal,
  • queue is the (optional) name Celery queue. It allows to dispatch signal calls to specialized queues: one for interactive functions (allowing short response times) and one for slow functions (real background tasks). queue can also be a sub-class of djangofloor.decorators.DynamicQueueName to dynamically choose the right Celery queue.
  • is_allowed_to must be a callable that determine whether if a signal call is allowed to a JS client. Given is_allowed_to(signal, window_info, kwargs), it must return True or False. It can be called in two ways: * when the JS client asks for a list of available signals (then kwargs is None), * when the JS tries to call a signal (then kwargs is of course a dict).

The last two arguments may be different for each Python code connected to the same signal. If all Python functions does not accept the same keyword arguments, then an exception will be raised, so they should accept **kwargs. In the following example, both functions will be executed. The first one will always be executed by the ‘celery’ queue, the second one will use a Celery queue dedicated to the user. When called from the JavaScript, the second code will only be executed if the client is authenticated.

from djangofloor.decorators import signal, everyone, is_authenticated, DynamicQueueName
@signal(is_allowed_to=everyone, path='demo.signal.name', queue='celery')
def slow_signal(window_info, kwarg1="demo", kwarg2: int=32):
   [perform a (clever) thing]

class UserQueueName(DynamicQueueName):

    def __call__(connection, window_info, kwargs):
       """return the name of the Celery queue (in this case, each user has its own Celery queue)
       """
       return getattr(window_info, 'username', 'celery')

@signal(is_allowed_to=is_authenticated, path='demo.signal.name', queue=UserQueueName())
def slow_signal(window_info, kwarg1='demo', kwarg3: bool=True, **kwargs):
   [perform a (clever) thing]

You must define your signals into yourproject/signals.py, or in any module that is imported by yourproject/signals.py.

Calling signals from Python

Calling signals is quite easy: just provide the window_info if the call is destined to a JS client, the name of the called signal, the destination (run on the server or the selected JS clients). If you do not want to immediately run the signal, you can use countdown, expires and eta options (please read the Celery documentation for their respective meanings).

from djangofloor.tasks import call, SERVER, WINDOW, USER
from django.contrib.auth.models import User

def my_view(request):
    u = User.objects.get(id=1)
    call(request, 'demo.signal.name', to=[SERVER, 42, 'test', u], kwargs={'kwarg1': "value", "kwarg2": 10}, countdown=None, expires=None, eta=None)

The destination can be one of the constants SERVER (), WINDOW, USER (all JS browser windows belonging to the connected user), BROADCAST (any JS client), or a list of any values. If SERVER is present, then the code will be executed on the server side (if such a signal is defined). All JS clients featuring the corresponding values will execute the signal, if the corresponding JS signal is defined!.

Defining JS signals

For using signals with JavaScript, you need to

  • add ‘/static/js/djangofloor-base.js’ to the list of loaded scripts,
  • use the df_init_websocket (for the djangofloor template library) tag anywhere in your HTML template,
  • use the set_websocket_topics(request, *topics) in the Django view – USER, WINDOW and BROADCAST are always added,
  • define some JS signal with $.df.connect(‘signal.name’, function(opts)).
# in your Django view
from djangofloor.tasks import set_websocket_topics
def my_view(request):
    [...]
    context = {...}
    set_websocket_topics(request)
    return TemplateResponse(request, template='template_name', context=context)
/* in your template */
{% load djangofloor staticfiles %}
{% static 'vendor/jquery/dist/jquery.min.js' %}
{% static 'js/djangofloor-base.js' %}
<script type="application/javascript">
    /* can be in a JS file */
    window.onload = function () {
        $.df.connect('signal.name', function (opts) {
            // opts is the JS equivalent of the Pythonic `**kwargs`
        });
    };
</script>
{% df_init_websocket %}

The first two steps are handled by the default template. A topic can be any Python value, serialized to a string by settings.WEBSOCKET_TOPIC_SERIALIZER (by default djangofloor.wsgi.topics.serialize_topic). When a signal is sent to a given topic, all JS clients featuring this topics receive this signal.

Under the hood, each HTTP request has a unique ID, which is associated to the list of topics stored in Redis via set_websocket_topics. The HTTP response is sent to the client and the actual websocket connection can be made with this unique ID and subscribed to its topic list (via Redis pub/sub).

Using signals from JS

Calling signals is simpler that creating a new one. Once the steps enumerated before are made, you just have to call it with $.df.call and to provide its name and its arguments. JS and allowed Python codes are all executed.

$.df.call('signal.name', {kwarg1: "value1", kwarg2: "value2"});

Calling signals on a set of browsers

You can call signals from Python on any set of browsers: all windows open by a given user, all open windows, only the window that initiated the connection… If your Django app is a blog, you should have a page per blog post and an index. You can send signals to all users viewing a specific post or the index page.

# in your Django view
from djangofloor.tasks import set_websocket_topics
from djangofloor.tasks import call, SERVER, WINDOW, USER
def index_view(request):
    set_websocket_topics(request, 'index')  # <- add the "index" property to this page
    return TemplateResponse(request, template='index.html', context={})
def post_view(request, post_id):
    post = Post.objects.get(id=post_id)
    set_websocket_topics(request, post)  # <- add the "post" property to this page
    return TemplateResponse(request, template='post.html', context={'post': post})

def display_message(post: Post):
    call(None, 'demo.signal.name', to=[post], kwargs={'kwarg1': "value"})  # 'demo.signal.name' must be defined in JS!
    call(None, 'demo.signal.name', to=["index"], kwargs={'kwarg1': "value"})  # 'demo.signal.name' must be defined in JS!

Built-in signals

DjangoFloor provides a set of Python and JavaScript signals. Most of them are JavaScript ones, allowing you to dynamically modify your HTML page from your Python code. All these JavaScript signals have shortcuts to ease their use: you can use autocompletion and easily check their arguments. Default Python signals are provided in djangofloor.signals. Shortcuts for many JavaScript signals are defined in djangofloor.signal.html and djangofloor.signals.bootstrap3. They allow you to call JavaScript code by only writing Python code.

Testing signals

The signal framework requires a working Redis and a worker process. However, if you only want to check if a signal has been called in unitary tests, you can use djangofloor.tests.SignalQueue. Both server-side and client-side signals are kept into memory:

  • djangofloor.tests.SignalQueue.ws_signals,
    • keys are the serialized topics
    • values are lists of tuples (signal name, arguments as dict)
  • djangofloor.tests.SignalQueue.python_signals
    • keys are the name of the queue
    • values are lists of (signal_name, window_info_dict, kwargs=None, from_client=False, serialized_client_topics=None, to_server=False, queue=None)
      • signal_name is … the name of the signal
      • window_info_dict is a WindowInfo serialized as a dict,
      • kwargs is a dict representing the signal arguments,
      • from_client is True if this signal has been emitted by a web browser,
      • serialized_client_topics is not None if this signal must be re-emitted to some client topics,
      • to_server is True if this signal must be processed server-side,
      • queue is the name of the selected Celery queue.
from djangofloor.tasks import scall, SERVER
from djangofloor.wsgi.window_info import WindowInfo
from djangofloor.wsgi.topics import serialize_topic
from djangofloor.decorators import signal
# noinspection PyUnusedLocal
@signal(path='test.signal', queue='demo-queue')
def test_signal(window_info, value=None):
    print(value)

wi = WindowInfo()
with SignalQueue() as fd:
    scall(wi, 'test.signal1', to=[SERVER, 1], value="value1")
    scall(wi, 'test.signal2', to=[SERVER, 1], value="value2")

# fd.python_signals looks like {'demo-queue': [ ['test.signal1', {…}, {'value': 'value1'}, False, None, True, None], ['test.signal2', {…}, {'value': 'value2'}, False, None, True, None]]}
# fd.ws_signals looks like {'-int.1': [('test.signal1', {'value': 'value1'}), ('test.signal2', {'value': 'value2'})]}

If you do not want to use djangofloor.tests.SignalQueue as a context manager, you can just call activate and deactivate methods.

Remote functions

DjangoFloor allows to call Python functions from your JavaScript code. A remote function must be a function taking window_info as first argument, prefixed by the djangofloor.decorators.function decorator. For each app in settings.INSTALLED_APPS, DjangoFloor tries to import app.functions for auto-discovering WS functions. If you want to write your WS functions into other modules, be sure that app.functions imports these modules.

Like the signals, the access of the Python functions can be restricted to some clients.

from djangofloor.decorators import function, everyone
@function(is_allowed_to=everyone, path='myproject.myfunc', queue='slow')
def my_function(window_info, arg=None):
   [perform a (clever) thing]
   return 42  # must be JSON-serializable

These functions are automatically exported as JavaScript functions in the $.dfws namespace:

$.dfws.myproject.myfunc({arg: 3123}).then(function(result) { alert(result); });

Monitoring view

_images/monitoring.png

DjangoFloor provides a view to check if everything is ok or to debug if something is wrong. This view is made of several widgets:

The list of widgets is defined in settings.DF_SYSTEM_CHECKS, so you can change the list of widgets. By default, this view is limited to superusers. You can easily disable this view by setting settings.DF_SYSTEM_CHECKS to an empty list. By default, this view is accessible on /df/monitoring/system_state/ or through the admin index.

Notification system

Sometimes, you want to display a system to your users, for example to warn users before an upgrade. You can use the djangofloor.models.Notification model for creating such notification. You can create notifications for everyone, any authenticated users or limit them to a given set of users (or groups of users). Notifications can be displayed only during a given time and repeated everytime the user visits a page or only once.

The Notification class provides a get_notifications class method that computes all notifications that should be displayed to the user.

If you use the base Bootstrap3 template provided by DjangoFloor, everything is ready to use; you just have to create Notification objects.

Authentication

There are many, many ways to authenticate users, and many, many Django packages that define authentication methods.

Password authentication

As you know, Django offers a model for users (django.contrib.auth.models.User). Of course, you can use it, but you must define views for creating new accounts, logging in and logging out. DjangoFloor comes with the required views (and templates looking like the admin site) for using it. DjangoFloor can also handle basic HTTP authentication (useful for API).

You can also add the package django-allauth. Again, DjangoFloor comes with the required templates (using the admin site css). HTTP basic authentication is disabled by default, but you can easily activate it:

Or you can only activate it when you deploy your app:

/etc/yourproject/settings.ini
[auth]
allow_basic_auth = true

By default, password authentication only uses the Django user database, but you can disable it (for example if you only use a LDAP authentication):

yourproject/defaults.py
DF_ALLOW_LOCAL_USERS = False

Or in the .ini file:

/etc/yourproject/settings.ini
[auth]
local_users = false

You can allow anonymous users to create their own account:

/etc/yourproject/settings.ini
[auth]
create_users = true
yourproject/defaults.py
DF_ALLOW_USER_CREATION = True

Reverse-proxy authentication

You reverse proxy (Apache or Nginx) can authenticate users for you and put then user name in a HTTP header (often REMOTE_USER). Since the header is set by the reverse proxy and not by the Python server itself, this HTTP header is renamed to HTTP_REMOTE_USER. These reverse proxies can handle any authentication methods, like Kerberos, GSSAPI, LDAP, Shibbolet, and so on. The djangofloor.middleware.DjangoFloorMiddleware middleware uses this HTTP header to authenticate users. The user is automatically created on its first connection (you can even automatically add him to several groups) if create_user is true. This method allows GSSAPI/Kerberos authentication. You can also configure the LDAP authentication if you want to retrieve user attributes (or its groups) from the LDAP server.

/etc/yourproject/settings.ini
[auth]
remote_user_header = HTTP-REMOTE-USER
remote_user_groups = Users,New Users
create_users = true

OAuth2 authentication

The package django-allauth perfectly handles OAuth2 authentication from many providers. Please check its own documentation. Of course, it must be installed separately (it is not a dependency of Djangofloor).

The following things are transparently modified:

  • INSTALLED_APPS will contain the list of all required Django apps,
  • allauth.urls is inserted in root urls,
  • allauth.account.auth_backends.AuthenticationBackend is added to authentication backends.

However, you still have to write HTML templates, as described in this documentation. You can add a new provider or display configured providers with the following commands:

yourproject-ctl social_authentications show
yourproject-ctl social_authentications add

You need to run the migrate command again to finalize the creation. The first command also displays the used configuration file. If you reinstall your server, just backup this file to avoid this manual process.

PAM authentication

You can authenticate your user against the local PAM database, just set in the config files and install “django-pam”:

/etc/yourproject/settings.ini
[auth]
pam = true

Radius authentication

You can also authenticate users by testing their password against a Radius server, if you have installed the “django-radius” package:

/etc/yourproject/settings.ini
[auth]
radius_server = 8.8.8.1
radius_port = 1812
radius_secret = secret

LDAP authentication

Everything is ready to transparently use django-auth-ldap to enable LDAP authentication. There are two modes for LDAP authentication:

  • a LDAP search is performed (to search the user and its groups) with a specific account, then a binding is performed to check the password,
  • a direct bind is performed with the user login/password and the user account is used to search its data.

Here is an example of configuration for the first method:

/etc/yourproject/settings.ini
[auth]
ldap_server_url = ldap://ldap.example.com
ldap_start_tls = false
ldap_user_search_base = ou=users,dc=example,dc=com
ldap_bind_dn = cn=admin,ou=users,dc=example,dc=com
ldap_bind_password = secret
ldap_filter = (uid=%%(user)s)

and for the second method:

/etc/yourproject/settings.ini
[auth]
ldap_server_url = ldap://ldap.example.com
ldap_start_tls = false
ldap_direct_bind = uid=%%(user)s,ou=users,dc=example,dc=com

You can also use some advanced features, for example for retrieving some user attributes from the LDAP, or for copying its groups:

/etc/yourproject/settings.ini
[auth]
ldap_first_name_attribute = givenName
ldap_email_attribute = email
ldap_last_name_attribute = sn
ldap_is_active_group = cn=active,ou=groups,dc=example,dc=com
ldap_is_staff_group = cn=staff,ou=groups,dc=example,dc=com
ldap_is_superuser_group = cn=admin,ou=groups,dc=example,dc=com
ldap_group_search_base = ou=groups,dc=example,dc=com
ldap_group_type = posix
ldap_mirror_groups = true

Main features… and how to disable them

Settings system

DjangoFloor allows to merge several files to define your settings:

  • DjangoFloor.conf.defaults that aims at providing good default values,
  • yourproject.defaults for your project-specific settings,
  • /etc/yourproject/settings.ini and /etc/yourproject/settings.py for installation-dependent settings,
  • ./local_settings.py and ./local_settings.ini setting files in the working directory.

You should define your project-wide settings in yourproject.defaults and the list of installation-specific settings in yourproject.iniconf. Development-specific settings (DEBUG = True!) can be written into local_settings.py and added to your source.

If you do not want to use this setting system: just set the DJANGO_SETTINGS_MODULE environment variable before running the scripts.

Deactivate redis

To disable background tasks, websockets and Redis cache, you must change these settings:

  • WEBSOCKET_URL = None
  • USE_CELERY = False

Deactivate signals over websockets

To disable only the use of websockets, you must set WEBSOCKET_URL to None.

Default URLs

By default, DjangoFloor provides a complete URL configuration:

  • index view given by DF_INDEX_VIEW (through ‘index’),

  • static and media files,

  • simple authentication pages that use django.contrib.auth.views (or allauth.account.views is USE_ALL_AUTH is True):

    • ‘df:login’,
    • ‘df:logout’,
    • ‘df:password_reset’,
    • ‘df:set_password’,
  • signal definitions through ‘df:signals’ if WEBSOCKET_URL is not empty,

  • monitoring view through ‘df:system_state’ if DF_SYSTEM_CHECKS is not empty,

  • global search view through ‘df:site_search’ if DF_SITE_SEARCH_VIEW is not empty,

  • favicon through ‘favicon’,

  • Django admin site,

  • javascript translation (i18n),

  • URLs for django-all—auth if at least one method is required,

  • Django-REST-Framework if the package is installed,

  • Django debug toolbar if the package is installed and DEBUG is set.

To this list is added the list provided by DF_URL_CONF.

If you prefer to use your own URL configuration, just set the ROOT_URLCONF setting.

Middleware and context processors

DjangoFloor offers custom middleware (djangofloor.middleware.DjangoFloorMiddleware) and context processors (djangofloor.context_processors.context_base). You can disable them by overriding the settings TEMPLATE_CONTEXT_PROCESSORS and MIDDLEWARE_CLASSES.

DjangoFloor also provides a backend for authenticating users (djangofloor.backends.DefaultGroupsRemoteUserBackend). Override the AUTHENTICATION_BACKENDS setting to disable it.

Django-Pipeline

DjangoFloor provides a valid configuration for Django-Pipeline with Bootstrap3, Font-Awesome and a few specific scripts or style sheets. Just override the PIPELINE setting to disable them.

Scripts

DjangoFloor provides an easy way to run Django, Celery or aiohttp commands. There are several main functions in djangofloor.scripts that automatically detect the name of your project from the script name and loads settings from the corresponding Python module (your_projects.defaults). A classical setup.py script should create ‘{your_project}-ctl = djangofloor.scripts:control’ as entry_points. If you want to use custom scripts, you just have to remove this line from your setup.py. You can also target ‘djangofloor.scripts:django’, ‘djangofloor.scripts:aiohttp’, ‘djangofloor.scripts:gunicorn’, ‘djangofloor.scripts:celery’.

Logs

DjangoFloor provides a log configuration based on:

  • the DEBUG mode (if True, everything is logged to the console),
  • the name of your package (DF_MODULE_NAME),
  • the name of the running script (SCRIPT_NAME),
  • the LOG_DIRECTORY value for storing infos and errors in rotated files,
  • the LOG_REMOTE_URL value for send errors to a syslog (or logd) server,
  • the LOG_REMOTE_ACCESS boolean (that determines if client accesses are also sent to the remote log server),
  • the SERVER_NAME variable (instead of having the component, you have the name of the server in the logs),
  • the SERVER_PORT variable (instead of having the component, you have the name of the server in the logs),
  • the list LOG_EXCLUDED_COMMANDS of commands that do not write logs,
  • the RAVEN_DSN, which is the Sentry DSN (a URL embedding login and password),

This log configuration is provided by djangofloor.log.LogConfiguration.

If Django is in DEBUG mode, then all logs are only written to stdout. Otherwise, if LOG_DIRECTORY is set, each command has its own file (and each Celery queue has its own file). If LOG_REMOTE_URL is set, everything is sent to logd or rsyslogd. All errors are also reported the admins by e-mail.

Here is a sample of log message:

cat ./django_data/log/easydemo-server-error.log
2017-11-18 21:51:23 [localhost:9000] [ERROR] error log message

Common errors

Here is a short description of the most common errors about the DjangoFloor features.

the page is downloaded instead of being displayed

Check if you use django.template.response.TemplateResponse instead of django.shortcuts import render_to_response().

JS and CSS files are missing

Did you use the collectstatic command?

invalid setting configuration

If something is wrong in your configuration, you can display the loaded configuration, the used config files and the missing ones. This command also shows which settings are defined in each config file.

myproject-ctl check -v 3
myproject-ctl config python -v 2

signals are not working

  • first, check the monitoring view: if it works, then your configuration is valid,
  • check if some Celery workers are working for the right Celery queue (only the “celery” is used by default, and queues can be dynamic!),
  • djangofloor.tasks.set_websocket_topics() is not used in the Django view,
  • if you just added a signal, you must reload the web page (since the JS looks for exported Python signals in a .js file),
  • the signal is not allowed to JavaScript calls (that is the default).

forms and signals

Assume that one of your signals expect a HTML form as argument. Your Python code should look like:

@signal(path='my.signal', is_allowed_to=is_authenticated)
def my_signal(window_info, extra_info=None, form: SerializedForm(MySpecialForm)=None):
    form is None
    form.is_bound
    form.is_valid()

There are multiple ways to call to call this signal:

<button onclick="return $.df.call('my.signal', {extra_info: 42});">
<button onclick="return $.df.call('my.signal', {form: null, extra_info: 42});">
<button onclick="return $.df.call('my.signal', {form: [], extra_info: 42});">
<form onsubmit="return $.df.call('my.signal', {form: $(this).serializeArray(), extra_info: 42});">

In the first case, the Python form variable is None. In the second one, the Python form variable is an unbound MySpecialForm. In the two last cases, the form variable is a bound MySpecialForm.

Javascript API

JavaScript signals using Bootstrap3

Default DjangoFloor templates are based on BootStrap3. These functions allows to display notifications with these templates. The div used by the modal is also emptied to force the update of its content when a new modal is displayed.

$.df.modal.show(opts)

Display a nice Bootstrap 3 modal window (!)

$.df.call('df.modal.show', {html: "Modal content!", width: "120px",
    onhide: {signal: "signal.name", options: {...}}});
Arguments:
  • html (string) – Content of the modal (put inside the “modal-content” div element
  • width (string) – Width of the display modal (optional)
  • onhide (object) – signal to call when this modal is hidden or closed (optional)
$.df.notify(opts)

Display a notification to the user. Multiple styles are available: modal windows, Growl-like notifications, page-wide banners or system notification (out of the browser).

$.df.call('df.notify', {content: "Hello, world!", level: "danger", style: "modal"});
Arguments:
  • content (string) – Message of the notification
  • title (string) – Title (displayed in bold) of the notification
  • timeout (int) – Number of milliseconds before the notification is hidden
  • style (string) – Method to use for displaying the notification (‘notification’, ‘banner’, ‘modal’, or ‘system’)
  • level (string) – Color of the notification: ‘info’, ‘default’, ‘success’, ‘danger’, ‘warning’.

Base API for signals and functions

Here is the complete JavaScript API provided by DjangoFloor.

$.df.call(signal, opts, id)

Call a signal. If the signal is also defined in the Python server and available to the user, then the Python signal is also triggered.

Arguments:
  • signal (string) – Name of the called signal.
  • opts (object) – Object with signal arguments.
  • id (string) – Unique id of each signal triggered by the server. Do not use it yourself.
Returns:

always false.

$.df.connect(signal, fn)

Connect a javascript code to the given signal name.

Arguments:
  • signal (string) – Name of the signal.
  • fn (function) – Function that takes a single object as argument. The properties of this object are the signal arguments.
Returns:

nothing.

$.df.serializeArray(form)

A customized version of the $.serializeArray that add a value for file input elements (the name of the selected file). Can be useful for sending data to SerializedForm and check if a form is valid (without actually sending the file).

$.df.uploadFile(url, fileSelector, progressSelector)

Upload a file to the provided URL and update a progress bar HTML5 element.

Arguments:
  • url (string) – the URL to call (often a Django view)
  • form (Form) – the form object
  • progressSelector (string) – a jQuery selector for the progress element to update (optional)
Returns:

always false

<form onsubmit="return $.df.uploadFile('/app/upload', this, '#myProgressBar');" method="POST" >
<input type="text" name="content">
</form>
<progress max="100" value="0" id="myProgressBar"></progress>
$.df.CsrfForm(form)

Add the CSRF token to a form as a hidden input. Always returns True so you can use it as the onsubmit attribute. Useful when the form has been generated without any CSRF input.

Arguments:
  • form (Form) – the form object
Returns:

always true

Here is an example:

<form onsubmit="return $.df.CsrfForm(this);" method="POST" >
<input type="text" name="content">
</form>;
html.content(opts)

set the HTML contents of every matched element.

$.df.call('html.content', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.add(opts)

Create a new jQuery object with elements added to the set of matched elements.

$.df.call('html.add', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.add_class(opts)

Adds the specified class(es) to each of the set of matched elements.

$.df.call('html.add_class', {selector: "#obj", class_name: "myclass"});
Arguments:
  • selector (string) – jQuery selector
  • class_name (string) – new class
html.add_attribute(opts)

Adds or replace the specified attribute(s) to each of the set of matched elements.

$.df.call('html.add_attribute', {selector: "#obj", attr_name: "title", attr_value: "value"});
Arguments:
  • selector (string) – jQuery selector
  • attr_name (string) – attribute name
  • attr_value (string) – attribute value
html.after(opts)

Insert content, specified by the parameter, after each element in the set of matched elements..

$.df.call('html.after', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.append(opts)

Insert content, specified by the parameter, to the end of each element in the set of matched elements.

$.df.call('html.append', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.before(opts)

Insert content, specified by the parameter, before each element in the set of matched elements..

$.df.call('html.before', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.empty(opts)

Remove all child nodes of the set of matched elements from the DOM.

$.df.call('html.empty', {selector: "#obj"});
Arguments:
  • selector (string) – jQuery selector
html.fade_out(opts)

Hide the matched elements by fading them to transparent.

$.df.call('html.fade_out', {selector: "#obj", duration: 400});
Arguments:
  • selector (string) – jQuery selector
  • duration (int) – number of milliseconds of the animation
html.fade_in(opts)

Display the matched elements by fading them to opaque.

$.df.call('html.fade_in', {selector: "#obj", duration: 400});
Arguments:
  • selector (string) – jQuery selector
  • duration (int) – number of milliseconds of the animation
html.fade_to(opts)

Adjust the opacity of the matched elements.

$.df.call('html.fade_to', {selector: "#obj", duration: 400, opacity: 0.5});
Arguments:
  • selector (string) – jQuery selector
  • duration (int) – number of milliseconds of the animation
  • opacity (float) – A number between 0 and 1 denoting the target opacity.
html.fade_toggle(opts)

Display or hide the matched elements by animating their opacity.

$.df.call('html.fade_toggle', {selector: "#obj", duration: 400});
Arguments:
  • selector (string) – jQuery selector
  • duration (int) – number of milliseconds of the animation
  • easing (string) – A string indicating which easing function to use for the transition.
html.focus(opts)

Set the focus to the matched element.

$.df.call('html.focus', {selector: "#obj"});
Arguments:
  • selector (string) – jQuery selector
html.hide(opts)

Hide the matched elements.

$.df.call('html.hide', {selector: "#obj"});
Arguments:
  • selector (string) – jQuery selector
html.prepend(opts)

Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.

$.df.call('html.prepend', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.remove(opts)

Remove the set of matched elements from the DOM.

$.df.call('html.remove', {selector: "#obj"});
Arguments:
  • selector (string) – jQuery selector
html.remove_class(opts)

Remove a single class, multiple classes, or all classes from each element in the set of matched elements.

$.df.call('html.remove_class', {selector: "#obj", class_name: "class"});
Arguments:
  • selector (string) – jQuery selector
  • class_name (string) – class to remove
html.remove_attr(opts)

Remove an attribute from each element in the set of matched elements.

$.df.call('html.remove_attr', {selector: "#obj", attr_name: "attr"});
Arguments:
  • selector (string) – jQuery selector
  • attr_name (string) – attribute to remove
html.replace_with(opts)

Replace each element in the set of matched elements with the provided new content.

$.df.call('html.replace_with', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.show(opts)

Shows the matched elements.

$.df.call('html.show', {selector: "#obj"});
Arguments:
  • selector (string) – jQuery selector
html.toggle(opts)

Display or hide the matched elements.

$.df.call('html.toggle', {selector: "#obj"});
Arguments:
  • selector (string) – jQuery selector
html.text(opts)

Set the text contents of the matched elements.

$.df.call('html.text', {selector: "#obj", content: "<span>hello</span>"});
Arguments:
  • selector (string) – jQuery selector
  • content (string) – new HTML content
html.trigger(opts)

Execute all handlers and behaviors attached to the matched elements for the given event type.

$.df.call('html.trigger', {selector: "#obj", event: "click"});
Arguments:
  • selector (string) – jQuery selector
  • event (string) – event to trigger
html.val(opts)

Set the value of every matched element.

$.df.call('html.val', {selector: "#obj", value: "value"});
Arguments:
  • selector (string) – jQuery selector
  • value (string) – new value
html.download_file(opts)

Force the client to download the given file.

$.df.call('html.download_file', {url: "http://example.org/test.zip", filename: "test.zip"});
Arguments:
  • url (string) – URL of the file
  • filename (string) – name of the file
df.validate.update_csrf(opts)

Update the CSRF token in all forms.

$.df.call('df.validate.update_csrf', {});
Arguments:
  • value (string) – New CSRF value

What’s next?

Here is a short list of external tools you could use.

Versionning

I like Github but of course you can use any other repository like Gitlab.

cd myproject
git remote add git@github.com:myself/myproject.git
git add .
git commit -m 'initial commit'
git push origin

Python packages

If your project is free or open-source, you really should consider to upload it on Pypi. You first have to create a config file for Pypi and register your project:

cat << EOF > ~/.pypirc
[pypi]
username=myself
password=mypassword
EOF
cd myproject
python setup.py register

When you are ready to upload your project:

cd myproject
python setup.py sdist upload

deb/rpm packages

There are several ways to distribute your application, like:

  • source Python files,
  • Docker files,
  • standard packages for your distribution (e.g. .deb files for Ubuntu and Debian). To avoid the packaging of all your dependencies (and conflicts with packages proposed by the distribution), only the Python 3 of your distribution is used: a virtualenv is created in /opt and packaged as-is. All dependencies are installed inside this virtualenv.

Here is the description of the whole process:

  • create a Vagrant box using the selected distribution (like “ubuntu/xenial64”),
  • install a virtual env inside /opt/myproject,
  • create an archive of your project (with python3 setup.py sdist in the current directory),
  • send this archive to the Vagrant box and install it in the virtual env,
  • create required directories, collect static files and create some files (like service files for systemd),
  • finally create the package using the fpm command.

You can optionally keep the Vagrant box (it should be destroyed at the end of the command) with –no-destroy and install the newly created package in this box with –run-package. If you use this latter option, open your favorite browser to http://localhost:10080/ to try your project. FPM supports many options. You can tweak its behaviour with a config file provided by the –config option. You can display an example of such config file with the –show-config (since this option requires to run almost the whole process, it can takes some time).

You can use sites like PackageCloud to host your public packages.

documentation

A complete documentation can be generated and customized to perfectly fit your needs. You should consider using ReadTheDocs, like most of modern Python packages, for hosting your doc.

API Documentation

Here is the documentation of the complete API.

djangofloor.admin

Admin models for created notifications

You can use the default admin view to create new notifications for your users. There are actions to activate or deactivate these notifications.

class djangofloor.admin.NotificationAdmin(model, admin_site)[source]
actions = ['activate', 'deactivate']
activate(request, queryset)[source]
content_short(obj)[source]
deactivate(request, queryset)[source]
exclude = ['author', 'icon']
filter_horizontal = ['destination_users', 'destination_groups']
list_display = ('title_short', 'content_short', 'is_active', 'not_before', 'not_after', 'level', 'auto_hide_seconds', 'display_mode', 'broadcast_mode', 'repeat_count')
list_filter = ('is_active', 'level', 'display_mode', 'broadcast_mode', 'not_before', 'not_after', 'repeat_count')
media
readonly_fields = []
save_model(request, obj, form, change)[source]
search_fields = ['title']
title_short(obj)[source]

djangofloor.backends

Miscelaneous backends

The provided authentication backend for django.contrib.auth only overrides the default Django one for remote users (users that are authenticated using a HTTP header like HTTP_REMOTE_USER). It automatically add several groups to newly-created users. Setting DF_DEFAULT_GROUPS is expected to be a list of group names.

class djangofloor.backends.DefaultGroupsRemoteUserBackend[source]

Add groups to new users. Based on django.contrib.auth.backends.RemoteUserBackend. Only overrides the configure_user method to add the required groups.

authenticate(*args, **kwargs)[source]
configure_user(user)[source]

Configures a user after creation and returns the updated user.

By default, returns the user unmodified; only add it to the default group.

create_unknown_user
ldap_backend

djangofloor.celery

load Celery and discover tasks

You should not use this module (or rename it), as it is only used to auto-discover tasks.

djangofloor.celery.setup_celery_logging(**kwargs)[source]

Use to setup the logs, overriding the default Celery configuration

djangofloor.checks

Define checks integrated to the Django check framework

Django offers a system check framework, but that is called only after the Django setup. However, settings based on callables (like djangofloor.conf.config_values.CallableSetting) can also trigger some django.core.checks.Warning during the setting loading. Just append them to the settings_check_results list to delay them and display them just after the Django setup.

djangofloor.checks.get_pipeline_requirements()[source]
djangofloor.checks.missing_package(package_name, desc='')[source]
djangofloor.checks.pipeline_check(app_configs, **kwargs)[source]

Check if dependencies used by django-pipeline are installed.

djangofloor.checks.settings_check(app_configs, **kwargs)[source]

djangofloor.conf.callables

Callables settings

Dynamic

class djangofloor.conf.callables.DefaultListenAddress(value)[source]
get_value(merger, provider_name: str, setting_name: str)[source]
class djangofloor.conf.callables.ExcludedDjangoCommands[source]
required_settings = ['DEVELOPMENT', 'USE_CELERY', 'DEBUG']
class djangofloor.conf.callables.InstalledApps[source]

Provide a complete INSTALLED_APPS list, transparently adding common third-party packages. Specifically handle apps required by django-allauth (one by allowed method).

common_third_parties = OrderedDict([('USE_DEBUG_TOOLBAR', 'debug_toolbar'), ('USE_PIPELINE', 'pipeline'), ('USE_REST_FRAMEWORK', 'rest_framework'), ('USE_PAM_AUTHENTICATION', 'django_pam'), ('RAVEN_DSN', 'raven.contrib.django.raven_compat')])
default_apps = ['django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.humanize', 'django.contrib.sitemaps', 'django.contrib.sites', ExpandIterable('DF_INSTALLED_APPS'), 'bootstrap3', 'djangofloor', 'django.contrib.staticfiles', 'django.contrib.admin']
process_django_allauth(settings_dict)[source]
process_third_parties(settings_dict)[source]
required_settings = ['ALLAUTH_PROVIDER_APPS', 'USE_ALL_AUTH', 'USE_DEBUG_TOOLBAR', 'USE_PIPELINE', 'USE_REST_FRAMEWORK', 'USE_PAM_AUTHENTICATION', 'RAVEN_DSN']
social_apps = {'allauth.socialaccount.providers.fxa', 'allauth.socialaccount.providers.amazon', 'allauth.socialaccount.providers.asana', 'allauth.socialaccount.providers.stripe', 'allauth.socialaccount.providers.openid', 'allauth.socialaccount.providers.flickr', 'allauth.socialaccount.providers.basecamp', 'allauth.socialaccount.providers.mailru', 'allauth.socialaccount.providers.douban', 'allauth.socialaccount.providers.robinhood', 'allauth.socialaccount.providers.twitch', 'allauth.socialaccount.providers.foursquare', 'allauth.socialaccount.providers.bitbucket', 'allauth.socialaccount.providers.bitly', 'allauth.socialaccount.providers.twentythreeandme', 'allauth.socialaccount.providers.instagram', 'allauth.socialaccount.providers.persona', 'allauth.socialaccount.providers.tumblr', 'allauth.socialaccount.providers.github', 'allauth.socialaccount.providers.windowslive', 'allauth.socialaccount.providers.soundcloud', 'allauth.socialaccount.providers.twitter', 'allauth.socialaccount.providers.linkedin', 'allauth.socialaccount.providers.coinbase', 'allauth.socialaccount.providers.weixin', 'allauth.socialaccount.providers.line', 'allauth.socialaccount.providers.weibo', 'allauth.socialaccount.providers.draugiem', 'allauth.socialaccount.providers.baidu', 'allauth.socialaccount.providers.stackexchange', 'allauth.socialaccount.providers.shopify', 'allauth.socialaccount.providers.vk', 'allauth.socialaccount.providers.orcid', 'allauth.socialaccount.providers.feedly', 'allauth.socialaccount.providers.paypal', 'allauth.socialaccount.providers.facebook', 'allauth.socialaccount.providers.digitalocean', 'allauth.socialaccount.providers.auth0', 'allauth.socialaccount.providers.discord', 'allauth.socialaccount.providers.bitbucket_oauth2', 'allauth.socialaccount.providers.edmodo', 'allauth.socialaccount.providers.hubic', 'allauth.socialaccount.providers.eveonline', 'allauth.socialaccount.providers.untappd', 'allauth.socialaccount.providers.mailchimp', 'allauth.socialaccount.providers.spotify', 'allauth.socialaccount.providers.evernote', 'allauth.socialaccount.providers.xing', 'allauth.socialaccount.providers.kakao', 'allauth.socialaccount.providers.gitlab', 'allauth.socialaccount.providers.slack', 'allauth.socialaccount.providers.angellist', 'allauth.socialaccount.providers.pinterest', 'allauth.socialaccount.providers.reddit', 'allauth.socialaccount.providers.odnoklassniki', 'allauth.socialaccount.providers.linkedin_oauth2', 'allauth.socialaccount.providers.google', 'allauth.socialaccount.providers.naver', 'allauth.socialaccount.providers.vimeo'}
class djangofloor.conf.callables.RedisSmartSetting(prefix='', env_variable='REDIS_URL', fmt='url', extra_values=None)[source]

Handle values required for Redis configuration, as well as Heroku’s standard environment variables. Can be used as djangofloor.conf.config_values.CallableSetting.

config_values = ['PROTOCOL', 'HOST', 'PORT', 'DB', 'PASSWORD']
djangofloor.conf.callables.allauth_provider_apps(settings_dict)[source]
djangofloor.conf.callables.allowed_hosts(settings_dict)[source]
djangofloor.conf.callables.cache_setting(settings_dict)[source]
Automatically compute cache settings:
  • if debug mode is set, then caching is disabled
  • if django_redis is available, then Redis is used for caching
  • else memory is used
Parameters:settings_dict
Returns:
djangofloor.conf.callables.database_engine(settings_dict)[source]

Allow to use aliases for database engines, as well as the default dotted name

djangofloor.conf.callables.databases(settings_dict)[source]

Build a complete DATABASES setting, taking into account the DATABASE_URL environment variable if present (used on the Heroku platform).

djangofloor.conf.callables.generate_secret_key(django_ready, length=60)[source]
djangofloor.conf.callables.project_name(settings_dict)[source]

Transform the base module name into a nicer project name

>>> project_name({'DF_MODULE_NAME': 'my_project'})
'My Project'
Parameters:settings_dict
Returns:
djangofloor.conf.callables.required_packages(settings_dict)[source]

Return a sorted list of the Python packages required by the current project (with their dependencies). A warning is added for each missing package.

Parameters:settings_dict
Returns:
djangofloor.conf.callables.secure_hsts_seconds(settings_dict)[source]
djangofloor.conf.callables.smart_hostname(settings_dict)[source]

By default, use the listen address and port as server name. Use the “HEROKU_APP_NAME” environment variable if present.

Parameters:settings_dict
Returns:
djangofloor.conf.callables.template_setting(settings_dict)[source]
djangofloor.conf.callables.url_parse_prefix(settings_dict)[source]

Return the public URL prefix, given the public base URL

>>> url_parse_prefix({'SERVER_BASE_URL': 'https://demo.example.org/demo/'})
'/demo/'
>>> url_parse_prefix({'SERVER_BASE_URL': 'http://demo.example.org/'})
'/'
>>> url_parse_prefix({'SERVER_BASE_URL': 'https://demo.example.org:8010'})
'/'
djangofloor.conf.callables.url_parse_server_name(settings_dict)[source]

Return the public hostname, given the public base URL

>>> url_parse_server_name({'SERVER_BASE_URL': 'https://demo.example.org/'})
'demo.example.org'
djangofloor.conf.callables.url_parse_server_port(settings_dict)[source]

Return the public port, given the public base URL

>>> url_parse_server_port({'SERVER_BASE_URL': 'https://demo.example.org/', 'USE_SSL': True})
443
>>> url_parse_server_port({'SERVER_BASE_URL': 'http://demo.example.org/', 'USE_SSL': False})
80
>>> url_parse_server_port({'SERVER_BASE_URL': 'https://demo.example.org:8010/', 'USE_SSL': True})
8010
djangofloor.conf.callables.url_parse_server_protocol(settings_dict)[source]

Return the public HTTP protocol, given the public base URL

>>> url_parse_server_protocol({'USE_SSL': True})
'https'
>>> url_parse_server_protocol({'USE_SSL': False})
'http'
djangofloor.conf.callables.url_parse_ssl(settings_dict)[source]

Return True if the public URL uses https

>>> url_parse_ssl({'SERVER_BASE_URL': 'https://demo.example.org/demo/'})
True
>>> url_parse_ssl({'SERVER_BASE_URL': 'http://demo.example.org/'})
False
djangofloor.conf.callables.use_x_forwarded_for(settings_dict)[source]

Return True if this server is assumed to be behind a reverse proxy. Heuristic: the external port (in SERVER_PORT) is different from the actually listened port (in LISTEN_ADDRESS).

>>> use_x_forwarded_for({'SERVER_PORT': 8000, 'LISTEN_ADDRESS': 'localhost:8000'})
False
>>> use_x_forwarded_for({'SERVER_PORT': 443, 'LISTEN_ADDRESS': 'localhost:8000'})
True

djangofloor.conf.config_values

Complex settings at runtime

Allow to define settings based on references to other settings (overriden in other config files).

Examples:

# file1: djangofloor.config.defaults
from djangofloor.config.config_values import SettingReference
DEBUG = False
TEMPLATE_DEBUG = SettingReference('DEBUG')
# file1: myproject.defaults
DEBUG = True

Since the second file overrides the first one, TEMPLATE_DEBUG always has the same value as DEBUG (True).

class djangofloor.conf.config_values.AutocreateFile(value, mode=None, *args, **kwargs)[source]

Represent a file name. Its parent directory is automatically created by the “collectstatic” command.

get_value(merger, provider_name: str, setting_name: str)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
class djangofloor.conf.config_values.AutocreateFileContent(value, create_function, mode=None, *args, **kwargs)[source]

Return the content of an existing file, or automatically write it and returns the content of the created file. Content must be a unicode string. The first argument of the provided create_function is the name of the file to create.

The file is only written when the “migrate” Django command is called. The first arg of the provided create_function is a bool, in addition of your own *args and **kwargs:

  • True when Django is ready and your function is called for writing the file during the “migrate” command
  • False when Django is not ready and your function is called for loading settings
get_value(merger, provider_name: str, setting_name: str)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
pre_collectstatic(merger, provider_name, setting_name, value)[source]
pre_migrate(merger, provider_name, setting_name, value)[source]
serialize_value(value) → str[source]

Serialize the result value to write it to the target file.

Parameters:value – the value returned by the create_function
Returns:
unserialize_value(value: str)[source]

Format the text read in the target file. :param value: the content of the file :return:

class djangofloor.conf.config_values.CallableSetting(value, *required)[source]

Require a function(kwargs) as argument, this function will be called with all already computed settings in a dict.

>>> SETTING_1 = True
>>> def inverse_value(values):
>>>     return not values['SETTING_1']
>>> SETTING_2 = CallableSetting(inverse_value, 'SETTING_1')

In local_settings.py

>>> SETTING_1 = False

In your code:

>>> from django.conf import settings
>>> if hasattr(settings, 'SETTING_2'):
>>>     assert(settings.SETTING_1 is False)  # default value is overriden by local_settings.py
>>>     assert(settings.SETTING_2 is True)  # SETTING_2 value is dynamically computed on startup

Extra arguments must be strings, that are interpreted as required settings, that must be available before the call to your function. You can also set an attribute called required_settings.

>>> def inverse_value(values):
>>>     return not values['SETTING_1']
>>> inverse_value.required_settings = ['SETTING_1']
>>> SETTING_2 = CallableSetting(inverse_value)
get_value(merger, provider_name: str, setting_name: str)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
class djangofloor.conf.config_values.ConfigValue(value)[source]

Base class for special setting values. When a setting is a djangofloor.settings.ConfigValue, then the method get_value(merger) is called for getting the definitive value.

get_value(merger, provider_name: str, setting_name: str)[source]

Return the intepreted value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
post_collectstatic(merger, provider_name, setting_name, value)[source]

Called after the “collectstatic” command

post_migrate(merger, provider_name, setting_name, value)[source]

Called after the “migrate” command

pre_collectstatic(merger, provider_name, setting_name, value)[source]

Called before the “collectstatic” command (at least the one provided by Djangofloor). Could be used for creating public files or directories (static files, required directories…).

pre_migrate(merger, provider_name, setting_name, value)[source]

Called before the “migrate” command. Could be used for creating private files (like the SECRET_KEY file)

class djangofloor.conf.config_values.DeprecatedSetting(value, default=None, msg='')[source]
get_value(merger, provider_name: str, setting_name: str)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
class djangofloor.conf.config_values.Directory(value, mode=None)[source]

Represent a directory on the filesystem, that is automatically created by the “migrate” and “collectstatic” commands

get_value(merger, provider_name: str, setting_name: str)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
pre_collectstatic(merger, provider_name, setting_name, value)[source]
pre_migrate(merger, provider_name, setting_name, value)[source]
class djangofloor.conf.config_values.ExpandIterable(value, func=None)[source]

Allow to import an existing list inside a list setting. in defaults.py:

>>> LIST_1 = [0, ]
>>> LIST_2 = [1, ExpandIterable('LIST_1'), 2, ]
>>> DICT_1 = {0: 0, }
>>> DICT_2 = {1: 1, None: ExpandIterable('DICT_1'), 2: 2, }

In case of dict, the key is ignored when the referenced dict is expanded. In local_settings.py

>>> LIST_1 = [3, ]
>>> DICT_1 = {3: 3, }

In your code:

>>> from django.conf import settings

Then settings.LIST_2 is equal to [1, 3, 2]. settings.DICT_2 is equal to {1: 1, 2: 2, 3: 3, }.

class djangofloor.conf.config_values.File(value, mode=None)[source]

Represent a file name. Its parent directory is automatically created by the “migrate” and “collectstatic” command.

get_value(merger, provider_name: str, setting_name: str)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
pre_collectstatic(merger, provider_name, setting_name, value)[source]
pre_migrate(merger, provider_name, setting_name, value)[source]
class djangofloor.conf.config_values.Path(value, mode=None)[source]

Represent any path on the filesystem.

chmod(merger, filename)[source]
get_value(merger, provider_name, setting_name)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
static makedirs(merger, dirname)[source]
class djangofloor.conf.config_values.RawValue(value)[source]

Return the value as-is. Since by defaults all string values are assumed to be formatted string, you need to use RawValue for using values that should be formatted.

from djangofloor.conf.config_values import RawValue
SETTING_1 = '{DEBUG}'  # will be transformed to 'True' or 'False'
SETTING_2 = Raw('{DEBUG}')  # will be kept as  '{DEBUG}'
get_value(merger, provider_name: str, setting_name: str)[source]

Return the non-intepreted value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value
class djangofloor.conf.config_values.SettingReference(value, func=None)[source]

Reference any setting object by its name. Allow to reuse a list defined in another setting file.

in defaults.py:

>>> SETTING_1 = True
>>> SETTING_2 = SettingReference('SETTING_1')

In local_settings.py

>>> SETTING_1 = False

In your code:

>>> from django.conf import settings

Then settings.SETTING_2 is equal to False

get_value(merger, provider_name: str, setting_name: str)[source]

Return the value

Parameters:
  • merger (djangofloor.utils.SettingMerger) – merger object, with all interpreted settings
  • provider_name – name of the provider containing this value
  • setting_name – name of the setting containing this value

djangofloor.conf.defaults

Default values for all Django settings

Define settings for deploying most of djangofloor-based websites or for running them in DEBUG mode. Most of them are used by Django, some of them by common third-party packages and the other ones are used by DjangoFloor.

DjangoFloor also allows references between settings: for example, you only defines SERVER_BASE_URL (like ‘https://www.example.com/site/’ ) and SERVER_NAME (‘www.example.com’), SERVER_PORT (‘443’), USE_SSL (‘True’), SERVER_PROTOCOL (‘https’) and URL_PREFIX (‘/site/’) are deduced.

These settings are defined in djangofloor.conf.defaults. Settings that should be customized on each installation (like the server name or the database password) can be written in .ini files. The mapping between the Python setting and the [section/option] system is defined in djangofloor.conf.mapping.

    project_name,
    allowed_hosts,
    cache_setting,
    template_setting,
    generate_secret_key,
    use_x_forwarded_for,
    required_packages,
    installed_apps,
    databases,
    excluded_django_commands,
    celery_redis_url,
    websocket_redis_dict,
    cache_redis_url,
    session_redis_dict,
    smart_hostname,
    DefaultListenAddress,
    allauth_provider_apps,
    secure_hsts_seconds)
from djangofloor.conf.config_values import (
    Path,
    Directory,
    SettingReference,
    ExpandIterable,
    CallableSetting,
    AutocreateFileContent,
    AutocreateFile,
)
from djangofloor.conf.pipeline import static_storage, pipeline_enabled
from djangofloor.log import log_configuration
from djangofloor.utils import is_package_present, guess_version

__author__ = "Matthieu Gallet"

# ######################################################################################################################
#
# detect if some external packages are available, to automatically customize some settings
#
# ######################################################################################################################
try:
    import django_redis  # does not work with is_package_present (???)

    USE_REDIS_CACHE = True
except ImportError:
    django_redis = None
    USE_REDIS_CACHE = False
USE_CELERY = is_package_present("celery")
USE_REDIS_SESSIONS = is_package_present("redis_sessions")
USE_PIPELINE = is_package_present("pipeline")
USE_DEBUG_TOOLBAR = is_package_present("debug_toolbar")
USE_REST_FRAMEWORK = is_package_present("rest_framework")
USE_ALL_AUTH = is_package_present("allauth")

# ######################################################################################################################
#
# settings that could be kept as-is for most projects
# of course, you can override them in your default settings
#
# ######################################################################################################################
ADMINS = (("admin", "{ADMIN_EMAIL}"),)
ALLOWED_HOSTS = CallableSetting(allowed_hosts)
CACHE_URL = CallableSetting(cache_redis_url)
CACHES = CallableSetting(cache_setting)
CSRF_COOKIE_DOMAIN = "{SERVER_NAME}"
CSRF_TRUSTED_ORIGINS = ["{SERVER_NAME}", "{SERVER_NAME}:{SERVER_PORT}"]
DATABASES = CallableSetting(databases)

DEBUG = False
# you should create a "local_settings.py" with "DEBUG = True" at the root of your project
DEVELOPMENT = True
# display all commands (like "migrate" or "runserver") in manage.py
# if False, development-specific commands are hidden

DEFAULT_FROM_EMAIL = "webmaster@{SERVER_NAME}"
FILE_UPLOAD_TEMP_DIR = Directory("{LOCAL_PATH}/tmp-uploads")
INSTALLED_APPS = CallableSetting(installed_apps)
LOGGING = CallableSetting(log_configuration)
MANAGERS = SettingReference("ADMINS")
MEDIA_ROOT = Directory("{LOCAL_PATH}/media")
MEDIA_URL = "/media/"
MIDDLEWARE = [
    "django.middleware.cache.UpdateCacheMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "djangofloor.middleware.DjangoFloorMiddleware",
    ExpandIterable("DF_MIDDLEWARE"),
    "django.middleware.cache.FetchFromCacheMiddleware",
]
if USE_DEBUG_TOOLBAR:
    MIDDLEWARE.insert(-3, "debug_toolbar.middleware.DebugToolbarMiddleware")

ROOT_URLCONF = "djangofloor.root_urls"
SECRET_KEY = AutocreateFileContent(
    "{LOCAL_PATH}/secret_key.txt", generate_secret_key, mode=0o600, length=60
)
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = SettingReference("USE_SSL")
SECURE_HSTS_PRELOAD = SettingReference("USE_SSL")
SECURE_HSTS_SECONDS = CallableSetting(secure_hsts_seconds)
SECURE_PROXY_SSL_HEADER = (
    "HTTP_X_FORWARDED_PROTO",
    "https",
)  # X-Forwarded-Proto or None
SECURE_SSL_REDIRECT = SettingReference("USE_SSL")
SECURE_FRAME_DENY = SettingReference("USE_SSL")
SERVER_EMAIL = "{ADMIN_EMAIL}"
SESSION_COOKIE_AGE = 1209600
TEMPLATES = CallableSetting(template_setting)
TEMPLATE_DEBUG = SettingReference("DEBUG")
TEMPLATE_DIRS = ()
TEMPLATE_CONTEXT_PROCESSORS = [
    "django.contrib.auth.context_processors.auth",
    "django.template.context_processors.debug",
    "django.template.context_processors.i18n",
    "django.template.context_processors.media",
    "django.template.context_processors.static",
    "django.template.context_processors.tz",
    "django.contrib.messages.context_processors.messages",
    "djangofloor.context_processors.context_base",
    ExpandIterable("DF_TEMPLATE_CONTEXT_PROCESSORS"),
]
TEST_RUNNER = "django.test.runner.DiscoverRunner"
USE_I18N = True
USE_L10N = True
USE_THOUSAND_SEPARATOR = True
USE_TZ = True
USE_X_FORWARDED_HOST = True  # X-Forwarded-Host
X_FRAME_OPTIONS = "SAMEORIGIN"
WSGI_APPLICATION = "djangofloor.wsgi.django_runserver.django_application"

# django.contrib.auth
AUTHENTICATION_BACKENDS = CallableSetting(authentication_backends)
LOGIN_URL = "/admin/login/"
LOGIN_REDIRECT_URL = "{URL_PREFIX}"
# LOGOUT_REDIRECT_URL = '{URL_PREFIX}'

# django.contrib.sessions
if USE_REDIS_SESSIONS:
    SESSION_ENGINE = "redis_sessions.session"
SESSION_COOKIE_SECURE = SettingReference("USE_SSL")
CSRF_COOKIE_SECURE = SettingReference("USE_SSL")

# django.contrib.sites
SITE_ID = 1

# django.contrib.staticfiles
STATIC_ROOT = Directory("{LOCAL_PATH}/static")
STATIC_URL = "/static/"
STATICFILES_STORAGE = CallableSetting(static_storage)
STATICFILES_FINDERS = [
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
]
if USE_PIPELINE:
    STATICFILES_FINDERS.append("pipeline.finders.PipelineFinder")

# celery
BROKER_URL = CallableSetting(celery_redis_url)
CELERY_DEFAULT_QUEUE = "celery"
CELERY_TIMEZONE = "{TIME_ZONE}"
CELERY_RESULT_EXCHANGE = "{DF_MODULE_NAME}_results"
CELERY_RESULT_BACKEND = "{BROKER_URL}"
CELERY_RESULT_SERIALIZER = "json"
CELERY_ACCEPT_CONTENT = ["json", "yaml", "msgpack"]
CELERY_APP = "djangofloor"
CELERY_CREATE_DIRS = True
CELERY_TASK_SERIALIZER = "json"

# django-npm
NPM_EXECUTABLE_PATH = "npm"
NPM_ROOT_PATH = Directory("{LOCAL_PATH}/npm")
NPM_STATIC_FILES_PREFIX = "vendor"

# djangofloor
DATA_PATH = Directory("{LOCAL_PATH}/data")
SERVER_NAME = CallableSetting(url_parse_server_name)  # ~ www.example.org
SERVER_PORT = CallableSetting(url_parse_server_port)  # ~ 443
SERVER_PROTOCOL = CallableSetting(url_parse_server_protocol)  # ~ "https"
URL_PREFIX = CallableSetting(
    url_parse_prefix
)  # something like "/prefix/" (most probably just "/")
USE_HTTP_BASIC_AUTH = False  # HTTP-Authorization
USE_SSL = CallableSetting(url_parse_ssl)  # ~ True
USE_X_FORWARDED_FOR = CallableSetting(use_x_forwarded_for)  # X-Forwarded-For
USE_X_SEND_FILE = False  # Apache module
X_ACCEL_REDIRECT = []  # paths used by nginx
DF_FAKE_AUTHENTICATION_USERNAME = None
DF_PROJECT_VERSION = CallableSetting(guess_version)
DF_REMOVED_DJANGO_COMMANDS = CallableSetting(excluded_django_commands)
DF_CHECKED_REQUIREMENTS = CallableSetting(required_packages)
DF_PUBLIC_SIGNAL_LIST = True
# do not check for each WS signal/function before sending its name to the client
DF_SYSTEM_CHECKS = [
    "djangofloor.views.monitoring.RequestCheck",
    "djangofloor.views.monitoring.AuthenticationCheck",
    "djangofloor.views.monitoring.System",
    "djangofloor.views.monitoring.CeleryStats",
    "djangofloor.views.monitoring.Packages",
    "djangofloor.views.monitoring.LogAndExceptionCheck",
    "djangofloor.views.monitoring.LogLastLines",
]
WINDOW_INFO_MIDDLEWARES = [
    "djangofloor.middleware.WindowKeyMiddleware",
    "djangofloor.middleware.DjangoAuthMiddleware",
    "djangofloor.middleware.Djangoi18nMiddleware",
    "djangofloor.middleware.BrowserMiddleware",
]
DF_SERVER_TIMEOUT = 35
DF_SERVER_GRACEFUL_TIMEOUT = 25
DF_SERVER_THREADS = 2
DF_SERVER_PROCESSES = 2
DF_SERVER_KEEPALIVE = 5
DF_SERVER_MAX_REQUESTS = 10000
DF_SERVER_SSL_KEY = None
DF_SERVER_SSL_CERTIFICATE = None
DF_ALLOW_USER_CREATION = True
DF_ALLOW_LOCAL_USERS = True

WEBSOCKET_URL = "/ws/"  # set to None if you do not use websockets
WEBSOCKET_REDIS_CONNECTION = CallableSetting(websocket_redis_dict)
WEBSOCKET_TOPIC_SERIALIZER = "djangofloor.wsgi.topics.serialize_topic"
WEBSOCKET_HEARTBEAT = "--HEARTBEAT--"
WEBSOCKET_SIGNAL_DECODER = "json.JSONDecoder"
WEBSOCKET_SIGNAL_ENCODER = "django.core.serializers.json.DjangoJSONEncoder"
WEBSOCKET_REDIS_PREFIX = "ws"
WEBSOCKET_REDIS_EXPIRE = 36000
WEBSOCKET_CONNECTION_EXPIRE = 3600  # by default, close a connection after one hour
# (but the client transparently reopen it
WEBSOCKET_HEADER = (
    "WINDOW_KEY"
)  # header used in AJAX requests (thus they have the same window identifier)

# django-pipeline
PIPELINE = {
    "PIPELINE_ENABLED": SettingReference("PIPELINE_ENABLED"),
    "JAVASCRIPT": SettingReference("PIPELINE_JS"),
    "STYLESHEETS": SettingReference("PIPELINE_CSS"),
    "CSS_COMPRESSOR": SettingReference("PIPELINE_CSS_COMPRESSOR"),
    "JS_COMPRESSOR": SettingReference("PIPELINE_JS_COMPRESSOR"),
    "COMPILERS": SettingReference("PIPELINE_COMPILERS"),
}
PIPELINE_COMPILERS = []
PIPELINE_CSS_COMPRESSOR = "pipeline.compressors.NoopCompressor"
PIPELINE_JS_COMPRESSOR = "pipeline.compressors.NoopCompressor"
PIPELINE_CSS = {
    "default": {
        "source_filenames": SettingReference("DF_CSS"),
        "output_filename": "css/default-all.css",
        "extra_context": {"media": "all"},
    },
    "django": {
        "source_filenames": [
            "vendor/font-awesome/css/font-awesome.min.css",
            "admin/css/forms.css",
            "css/djangofloor-django.css",
        ],
        "output_filename": "css/django-all.css",
        "extra_context": {"media": "all"},
    },
    "bootstrap3": {
        "source_filenames": [
            "vendor/bootstrap3/dist/css/bootstrap.min.css",
            "vendor/bootstrap3/dist/css/bootstrap-theme.min.css",
            "vendor/font-awesome/css/font-awesome.min.css",
            "css/djangofloor-bootstrap3.css",
            ExpandIterable("DF_CSS"),
        ],
        "output_filename": "css/bootstrap3-all.css",
        "extra_context": {"media": "all"},
    },
    "ie9": {
        "source_filenames": [],
        "output_filename": "css/ie9.css",
        "extra_context": {"media": "all"},
    },
}
PIPELINE_ENABLED = CallableSetting(pipeline_enabled)
PIPELINE_JS = {
    "default": {
        "source_filenames": [
            "vendor/jquery/dist/jquery.min.js",
            "js/djangofloor-base.js",
            ExpandIterable("DF_JS"),
        ],
        "output_filename": "js/default.js",
    },
    "django": {
        "source_filenames": [
            "vendor/jquery/dist/jquery.min.js",
            "js/djangofloor-base.js",
            "vendor/bootstrap-notify/bootstrap-notify.js",
            "js/djangofloor-django.js",
            ExpandIterable("DF_JS"),
        ],
        "output_filename": "js/django.js",
    },
    "bootstrap3": {
        "source_filenames": [
            "vendor/jquery/dist/jquery.js",
            "vendor/bootstrap3/dist/js/bootstrap.js",
            "js/djangofloor-base.js",
            "vendor/bootstrap-notify/bootstrap-notify.js",
            "js/djangofloor-bootstrap3.js",
            ExpandIterable("DF_JS"),
        ],
        "output_filename": "js/bootstrap3.js",
    },
    "ie9": {
        "source_filenames": [
            "vendor/html5shiv/dist/html5shiv.js",
            "vendor/respond.js/dest/respond.src.js",
        ],
        "output_filename": "js/ie9.js",
    },
}
PIPELINE_MIMETYPES = (
    (b"text/coffeescript", ".coffee"),
    (b"text/less", ".less"),
    (b"text/javascript", ".js"),
    (b"text/x-sass", ".sass"),
    (b"text/x-scss", ".scss"),
)
LIVE_SCRIPT_BINARY = "lsc"
LESS_BINARY = "lessc"
SASS_BINARY = "sass"
STYLUS_BINARY = "stylus"
BABEL_BINARY = "babel"
YUGLIFY_BINARY = "yuglify"
YUI_BINARY = "yuicompressor"
CLOSURE_BINARY = "closure"
UGLIFYJS_BINARY = "uglifyjs"
CSSTIDY_BINARY = "csstidy"
CSSMIN_BINARY = "cssmin"
TYPESCRIPT_BINARY = "tsc"
TYPESCRIPT_ARGUMENTS = []
# Django-All-Auth
ACCOUNT_EMAIL_SUBJECT_PREFIX = "[{SERVER_NAME}] "
ACCOUNT_EMAIL_VERIFICATION = None
ALLAUTH_PROVIDER_APPS = CallableSetting(allauth_provider_apps)
ALLAUTH_APPLICATIONS_CONFIG = AutocreateFile("{LOCAL_PATH}/social_auth.ini", mode=0o600)
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_AUTHENTICATION_METHOD = "username_email"
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "{SERVER_PROTOCOL}"
ACCOUNT_ADAPTER = "djangofloor.views.allauth.AccountAdapter"

# Django-Debug-Toolbar
DEBUG_TOOLBAR_CONFIG = {"JQUERY_URL": "{STATIC_URL}vendor/jquery/dist/jquery.min.js"}
DEBUG_TOOLBAR_PATCH_SETTINGS = False
DEBUG_TOOLBAR_PANELS = [
    "debug_toolbar.panels.versions.VersionsPanel",
    "debug_toolbar.panels.timer.TimerPanel",
    "debug_toolbar.panels.settings.SettingsPanel",
    "debug_toolbar.panels.profiling.ProfilingPanel",
    "debug_toolbar.panels.headers.HeadersPanel",
    "debug_toolbar.panels.request.RequestPanel",
    "debug_toolbar.panels.sql.SQLPanel",
    "debug_toolbar.panels.templates.TemplatesPanel",
    "debug_toolbar.panels.staticfiles.StaticFilesPanel",
    "debug_toolbar.panels.cache.CachePanel",
    "debug_toolbar.panels.signals.SignalsPanel",
    "debug_toolbar.panels.redirects.RedirectsPanel",
]
INTERNAL_IPS = ("127.0.0.1", "::1", "localhost")

# Django-Bootstrap3
BOOTSTRAP3 = {
    "jquery_url": "{STATIC_URL}vendor/jquery/dist/jquery.min.js",
    "base_url": "{STATIC_URL}vendor/bootstrap3/dist/",
    "theme_url": None,
    "include_jquery": False,
    "horizontal_label_class": "col-md-3",
    "horizontal_field_class": "col-md-9",
    "set_disabled": True,
    "set_placeholder": False,
    "formset_renderers": {"default": "bootstrap3.renderers.FormsetRenderer"},
    "form_renderers": {"default": "bootstrap3.renderers.FormRenderer"},
    "field_renderers": {
        "default": "bootstrap3.renderers.FieldRenderer",
        "inline": "bootstrap3.renderers.InlineFieldRenderer",
    },
}

# django-auth-ldap
AUTH_LDAP_SERVER_URI = None
AUTH_LDAP_BIND_DN = ""
AUTH_LDAP_BIND_PASSWORD = ""
AUTH_LDAP_USER_SEARCH_BASE = "ou=users,dc=example,dc=com"
AUTH_LDAP_FILTER = "(uid=%(user)s)"
AUTH_LDAP_USER_SEARCH = CallableSetting(ldap_user_search)
AUTH_LDAP_USER_DN_TEMPLATE = None
AUTH_LDAP_START_TLS = False
AUTH_LDAP_USER_ATTR_MAP = CallableSetting(ldap_attribute_map)
AUTH_LDAP_USER_FLAGS_BY_GROUP = CallableSetting(ldap_boolean_attribute_map)
AUTH_LDAP_MIRROR_GROUPS = False
AUTH_LDAP_USER_IS_ACTIVE = None
AUTH_LDAP_USER_IS_STAFF = None
AUTH_LDAP_USER_IS_SUPERUSER = None
AUTH_LDAP_USER_FIRST_NAME = None
AUTH_LDAP_USER_LAST_NAME = None
AUTH_LDAP_USER_EMAIL = None
AUTH_LDAP_GROUP_TYPE = CallableSetting(ldap_group_class)
AUTH_LDAP_GROUP_NAME = "posix"
AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_REQUIRE_GROUP = None
AUTH_LDAP_DENY_GROUP = None
# Cache group memberships for an hour to minimize LDAP traffic
AUTH_LDAP_CACHE_GROUPS = True
AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
# Use LDAP group membership to calculate group permissions.
AUTH_LDAP_FIND_GROUP_PERMS = False
AUTH_LDAP_GROUP_SEARCH = CallableSetting(ldap_group_search)
AUTH_LDAP_GROUP_SEARCH_BASE = "ou=groups,dc=example,dc=com"
AUTH_LDAP_AUTHORIZE_ALL_USERS = True
# https://bitbucket.org/illocution/django-auth-ldap/pull-requests/29/kerberos-bind-method-to-provide-multi/diff
# KRB5_CCACHE = None
# KRB5_KEYTAB = None
# KRB5_PRINCIPAL = None
# django-cors-headers
CORS_ORIGIN_WHITELIST = ("{SERVER_NAME}", "{SERVER_NAME}:{SERVER_PORT}")
CORS_REPLACE_HTTPS_REFERER = False

# django-hosts
DEFAULT_HOST = "{SERVER_NAME}"
HOST_SCHEME = "{SERVER_PROTOCOL}://"
HOST_PORT = "{SERVER_PORT}"

# django—pam
USE_PAM_AUTHENTICATION = False

# django-radius
RADIUS_SERVER = None
RADIUS_PORT = None
RADIUS_SECRET = None

# django-redis-sessions
SESSION_REDIS = CallableSetting(session_redis_dict)

# raven
RAVEN_CONFIG = {"dsn": "{RAVEN_DSN}", "release": "{DF_PROJECT_VERSION}"}

# ######################################################################################################################
#
# settings that should be customized for each project
# of course, you can redefine or override any setting
#
# ######################################################################################################################
# djangofloor
DF_CSS = []
DF_JS = []
DF_INDEX_VIEW = "djangofloor.views.IndexView"
DF_SITE_SEARCH_VIEW = None  # 'djangofloor.views.search.UserSearchView'
DF_PROJECT_NAME = CallableSetting(project_name)
DF_URL_CONF = "{DF_MODULE_NAME}.urls.urlpatterns"
DF_ADMIN_SITE = "django.contrib.admin.site"
DF_JS_CATALOG_VIEWS = ["djangofloor", "django.contrib.admin"]
# noinspection PyUnresolvedReferences
DF_INSTALLED_APPS = ["{DF_MODULE_NAME}"]  # your django app!
DF_PIP_NAME = (
    "{DF_MODULE_NAME}"
)  # anything such that "pip install {DF_PIP_NAME}" installs your project
# only used in docs
DF_MIDDLEWARE = []
DF_REMOTE_USER_HEADER = None  # HTTP_REMOTE_USER
DF_DEFAULT_GROUPS = [_("Users")]
DF_TEMPLATE_CONTEXT_PROCESSORS = []
NPM_FILE_PATTERNS = {
    "bootstrap-notify": ["*.js"],
    "bootstrap3": ["dist/*"],
    "font-awesome": ["css/*", "fonts/*"],
    "html5shiv": ["dist/*"],
    "jquery": ["dist/*"],
    "jquery-file-upload": ["css/*", "js/*"],
    # 'metro-ui': ['build/*'],
    "respond.js": ["dest/*"],
}
# used by the "npm" command: downloads these packages and copies the files matching any pattern in the list
LOG_REMOTE_ACCESS = True
LOG_DIRECTORY = Directory("{LOCAL_PATH}/log")
LOG_EXCLUDED_COMMANDS = {
    "clearsessions",
    "check",
    "compilemessages",
    "collectstatic",
    "config",
    "createcachetable",
    "changepassword",
    "createsuperuser",
    "dumpdb",
    "dbshell",
    "dumpdata",
    "flush",
    "loaddata",
    "gen_dev_files",
    "inspectdb",
    "makemessages",
    "makemigrations",
    "migrate",
    "npm",
    "packaging",
    "ping_google",
    "remove_stale_contenttypes",
    "sendtestemail",
    "shell",
    "showmigrations",
    "sqlflush",
    "sqlmigrate",
    "sqlsequencereset",
    "squashmigrations",
    "startapp",
    "test",
    "testserver",
}

# ######################################################################################################################
#
# settings that should be customized for each deployment
# {DF_MODULE_NAME}.iniconf:INI_MAPPING should be a list of ConfigField, allowing to define these settings in a .ini file
#
# ######################################################################################################################
ADMIN_EMAIL = "admin@{SERVER_NAME}"  # aliased in settings.ini as "[global]admin_email"
DATABASE_ENGINE = "sqlite3"  # aliased in settings.ini as "[database]engine"
DATABASE_NAME = Path(
    "{LOCAL_PATH}/database.sqlite3"
)  # aliased in settings.ini as "[database]name"
DATABASE_USER = ""  # aliased in settings.ini as "[database]user"
DATABASE_PASSWORD = ""  # aliased in settings.ini as "[database]password"
DATABASE_HOST = ""  # aliased in settings.ini as "[database]host"
DATABASE_PORT = ""  # aliased in settings.ini as "[database]port"
DATABASE_OPTIONS = {}
EMAIL_HOST = "localhost"  # aliased in settings.ini as "[email]host"
EMAIL_HOST_PASSWORD = ""  # aliased in settings.ini as "[email]password"
EMAIL_HOST_USER = ""  # aliased in settings.ini as "[email]user"
EMAIL_FROM = "{ADMIN_EMAIL}"  # aliased in settings.ini as "[email]from"
EMAIL_PORT = 25  # aliased in settings.ini as "[email]port"
EMAIL_SUBJECT_PREFIX = "[{SERVER_NAME}] "
EMAIL_USE_TLS = False  # aliased in settings.ini as "[email]use_tls"
EMAIL_USE_SSL = False  # aliased in settings.ini as "[email]use_ssl"
EMAIL_SSL_CERTFILE = None
EMAIL_SSL_KEYFILE = None
LANGUAGE_CODE = "fr-fr"  # aliased in settings.ini as "[global]language_code"
TIME_ZONE = "Europe/Paris"  # aliased in settings.ini as "[global]time_zone"
LOG_REMOTE_URL = None  # aliased in settings.ini as "[global]log_remote_url"
SERVER_BASE_URL = CallableSetting(
    smart_hostname
)  # aliased in settings.ini as "[global]server_url"

# djangofloor
LISTEN_ADDRESS = DefaultListenAddress(
    9000
)  # aliased in settings.ini as "[global]listen_address"
LOCAL_PATH = "./django_data"  # aliased in settings.ini as "[global]data"
__split_path = __file__.split(os.path.sep)
if "lib" in __split_path:
    prefix = os.path.join(*__split_path[: __split_path.index("lib")])
    LOCAL_PATH = Directory("/%s/var/{DF_MODULE_NAME}" % prefix)
# these Django commands do not write log (only on stdout)
# PID_DIRECTORY = Directory('{LOCAL_PATH}/run')
# PID_FILENAME = CallableSetting(pid_filename)

# django-redis-sessions
SESSION_REDIS_PROTOCOL = "redis"
SESSION_REDIS_HOST = "localhost"  # aliased in settings.ini as "[session]host"
SESSION_REDIS_PORT = 6379  # aliased in settings.ini as "[session]port"
SESSION_REDIS_DB = 1  # aliased in settings.ini as "[session]db"
SESSION_REDIS_PASSWORD = ""  # aliased in settings.ini as "[session]password"

# django_redis (cache)
CACHE_PROTOCOL = "redis"
CACHE_HOST = "localhost"  # aliased in settings.ini as "[cache]host"
CACHE_PORT = 6379  # aliased in settings.ini as "[cache]port"
CACHE_DB = 2  # aliased in settings.ini as "[cache]db"
CACHE_PASSWORD = ""  # aliased in settings.ini as "[cache]password"

# websockets
WEBSOCKET_REDIS_PROTOCOL = "redis"
WEBSOCKET_REDIS_HOST = "localhost"  # aliased in settings.ini as "[websocket]host"
WEBSOCKET_REDIS_PORT = 6379  # aliased in settings.ini as "[websocket]port"
WEBSOCKET_REDIS_DB = 3  # aliased in settings.ini as "[websocket]db"
WEBSOCKET_REDIS_PASSWORD = ""  # aliased in settings.ini as "[websocket]password"

# celery
CELERY_PROTOCOL = "redis"
CELERY_HOST = "localhost"  # aliased in settings.ini as "[celery]host"
CELERY_PORT = 6379  # aliased in settings.ini as "[celery]port"
CELERY_DB = 4  # aliased in settings.ini as "[celery]db"
CELERY_PASSWORD = ""  # aliased in settings.ini as "[celery]password"
CELERY_PROCESSES = 4

# raven
RAVEN_DSN = None

djangofloor.conf.fields

Convert values from config files to Python values

Use these classes in your mapping provided in yourproject.iniconf:INI_MAPPING. Check djangofloor.conf.mapping for examples.

class djangofloor.conf.fields.BooleanConfigField(name, setting_name, allow_none=False, **kwargs)[source]

Search for a boolean value in the ini file. If this value is empty and allow_none is True, then the value is None. Otherwise returns True if the provided (lower-cased) text is one of (‘1’, ‘ok’, ‘yes’, ‘true’, ‘on’)

class djangofloor.conf.fields.CharConfigField(name, setting_name, allow_none=True, **kwargs)[source]

Accepts str values. If allow_none, then None replaces any empty value.

class djangofloor.conf.fields.ChoiceConfigFile(name, setting_name, choices, help_str='', **kwargs)[source]

Only allow a limited set of values in the .ini file. The available values must be given as str.

Choices must be a dict, mapping .ini (string) values to actual values.

If an invalid value is provided by the user, then None is returned, but an error is displayed through the Django check system.

class djangofloor.conf.fields.ConfigField(name: str, setting_name: str, from_str=<class 'str'>, to_str=<function str_or_blank>, help_str: str = None, default: object = None)[source]

Class that maps an option in a .ini file to a setting.

Parameters:
  • name – the section and the option in a .ini file (like “database.engine”)
  • setting_name – the name of the setting (like “DATABASE_ENGINE”)
  • from_str (callable) – any callable that takes a text value and returns an object. Default to str_or_none
  • to_str (callable) – any callable that takes the Python value and that converts it to str. Default to str
  • help_str – any text that can serve has help in documentation.
  • default – the value that will be used in documentation. The current setting value is used if equal to None.
class djangofloor.conf.fields.FloatConfigField(name, setting_name, allow_none=True, **kwargs)[source]

Accept floating-point values. If allow_none, then None replaces any empty values (other 0.0 is used).

class djangofloor.conf.fields.IntegerConfigField(name, setting_name, allow_none=True, **kwargs)[source]

Accept integer values. If allow_none, then None replaces any empty values (other 0 is used).

class djangofloor.conf.fields.ListConfigField(name, setting_name, **kwargs)[source]

Convert a string to a list of values, splitted with the djangofloor.conf.fields.strip_split() function.

djangofloor.conf.fields.bool_setting(value)[source]

return True if the provided (lower-cased) text is one of (‘1’, ‘ok’, ‘yes’, ‘true’, ‘on’)

djangofloor.conf.fields.guess_relative_path(value)[source]

Replace an absolute path by its relative path if the abspath begins by the current dir

djangofloor.conf.fields.str_or_blank(value)[source]

return ‘’ if the provided value is None, else return value

djangofloor.conf.fields.str_or_none(text)[source]

return None if the text is empty, else returns the text

djangofloor.conf.fields.strip_split(value)[source]

Split the value on “,” and strip spaces of the result. Remove empty values.

>>> strip_split('keyword1, keyword2 ,,keyword3')
["keyword1", "keyword2", "keyword3"]
>>> strip_split('')
[]
>>> strip_split(None)
[]
Parameters:value
Returns:a list of strings
Return type:list

djangofloor.conf.mapping

Mapping between .ini config files and Django settings

This mapping can be overriden in yourproject.iniconf:INI_MAPPING. The INI_MAPPING must be a list of djangofloor.conf.fields.ConfigField, giving where to search in a .ini file, the corresponding Django setting value and how to convert from one format to the another.

djangofloor.conf.mapping.x_accel_converter(value)[source]

djangofloor.conf.merger

Classes and functions used for the DjangoFloor settings system

Define several helpers classes and internal functions for the DjangoFloor settings system, allowing to merge settings from different sources. This file must be importable while Django is not loaded yet.

class djangofloor.conf.merger.SettingMerger(fields_provider, providers, extra_values=None, stdout=None, stderr=None, no_color=False)[source]

Load different settings modules and config files and merge them.

add_provider(provider)[source]
analyze_raw_value(obj, provider_name, setting_name)[source]

Parse the object for replacing variables by their values.

If obj is a string like “THIS_IS_{TEXT}”, search for a setting named “TEXT” and replace {TEXT} by its value (say, “VALUE”). The returned object is then equal to “THIS_IS_VALUE”. If obj is a list, a set, a tuple or a dict, its components are recursively parsed. If obj is a subclass of djangofloor.conf.config_values.ConfigValue, its value is on-the-fly computed. Otherwise, obj is returned as-is.

Parameters:
  • obj – object to analyze
  • provider_name – the name of the config file
  • setting_name – the name of the setting containing this value but this value can be inside a dict or a list (like SETTING = [Directory(“/tmp”), ])
Returns:

the parsed setting

call_method_on_config_values(method_name: str)[source]

Scan all settings, looking for django.conf.config_values.ConfigValue and calling one of their methods.

Parameters:method_name – ‘pre_collectstatic’, ‘pre_migrate’, ‘post_collectstatic’, or ‘post_migrate’.
get_setting_value(setting_name)[source]
has_setting_value(setting_name)[source]
load_raw_settings()[source]
load_settings()[source]
post_process()[source]

Perform some cleaning on settings:

  • remove duplicates in INSTALLED_APPS (keeps only the first occurrence)
process()[source]
write_provider(provider, include_doc=False)[source]

djangofloor.conf.providers

Providers of Django settings

Settings that are merged to provide the final Django settings come from different kinds of sources (Python modules or files, ini files, …).

class djangofloor.conf.providers.ConfigFieldsProvider[source]

Provides a list of djangofloor.conf.fields.ConfigField. Used for retrieving settings from a config file.

get_config_fields()[source]

Return a list of config fields

name = None
class djangofloor.conf.providers.ConfigProvider[source]

Base class of config provider.

get_extra_settings()[source]

Return all settings internally defined.

Returns:an iterable of (setting_name, value)
get_value(config_field)[source]

Get the internal value if the config field is present in its internal values. Otherwise returns the current value of the config field.

has_value(config_field)[source]

Return True if a config_field is present in the file

is_valid()[source]

Return True if the provider is valid (for example, the corresponding file is missing).

name = None
set_value(config_field, include_doc=False)[source]

Get the value of the config_field and set its internal value

to_str()[source]

Convert all its internal values to a string

class djangofloor.conf.providers.DictProvider(values)[source]

Use a plain Python dict as a setting provider

get_extra_settings()[source]

Return all uppercase keys of the internal dict as valid filenames

get_value(config_field)[source]

get a value from the internal dict if present

has_value(config_field)[source]

check if the value is present in the internal dict

is_valid()[source]

always True

name = 'dict'
set_value(config_field, include_doc=False)[source]

modify the internal dict for storing the value

to_str()[source]

display the internal dict

class djangofloor.conf.providers.IniConfigProvider(config_file=None)[source]

Read a config file using the .ini syntax.

get_extra_settings()[source]

No extra setting can be defined in a config file

get_value(config_field: djangofloor.conf.fields.ConfigField)[source]

get option from the config file

has_value(config_field: djangofloor.conf.fields.ConfigField)[source]

return True if the option is defined in the config file

is_valid()[source]

Return True if the config file exists

name = '.ini file'
set_value(config_field: djangofloor.conf.fields.ConfigField, include_doc: bool = False)[source]

update the internal config file

to_str()[source]

Display the config file

class djangofloor.conf.providers.PythonConfigFieldsProvider(value=None)[source]

Provide a list of djangofloor.conf.fields.ConfigField from an attribute in a Python module.

get_config_fields()[source]

Return the list that is defined in the module by the attribute name

name = 'Python attribute'
class djangofloor.conf.providers.PythonFileProvider(module_filename)[source]

Load a Python module from the filename

name = 'Python file'
class djangofloor.conf.providers.PythonModuleProvider(module_name=None)[source]

Load a Python module from its dotted name

get_extra_settings()[source]

Return all values that look like a Django setting (i.e. uppercase variables)

get_value(config_field)[source]

Get the value of a variable defined in the Python module.

has_value(config_field)[source]

True if the corresponding variable is defined in the module

is_valid()[source]

Return True if the module can be imported

name = 'Python module'
set_value(config_field, include_doc=False)[source]

Set the value of the config field in an internal dict

to_str()[source]

Display values as if set in a Python module

djangofloor.conf.settings

Classes and functions used for the DjangoFloor settings system

Define several helpers classes and internal functions for the DjangoFloor settings system, allowing to merge settings from different sources. This file must be importable while Django is not loaded yet.

djangofloor.context_processors

DjangoFloor’s specific context processors

Add some values to the template context, related to:

  • (enabled or disabled) default views provided by DjangoFloor,
  • notification objects,
  • authentication provided by HTTP headers,
  • websockets
djangofloor.context_processors.context_base(request)[source]

Provide the following variables to templates when you RequestContext:

  • df_has_index_view: True if an default index view is included
  • df_has_monitoring_view: True if the monitoring view is included
  • df_has_site_search_view: True if a site-wide search view is provided
  • df_project_name: name of your project
  • df_remote_username: username provided by a HTTP header
  • df_remote_authenticated: True if the user authenticated by a HTTP header
  • df_get_notifications: when used, return the list of Notifications of the user
  • df_user_agent: user agent provided by the HttpRequest
  • df_window_key: a random value provided by each HttpRequest (allowing to identify each browser window)
  • df_has_ws_topics: True if some websockets topics are provided to the HttpRequest
Parameters:request (django.http.HttpRequest) – a HTTP request
Returns:a dict to update the global template context
Return type:dict

djangofloor.decorators

Decorators to declare signals and remote functions

ALso define common functions for allowing (or not) signal calls to user, and several tools for checking arguments provided to the signal (or function).

Decorators

Three decorators are provided, for creating signals, websocket functions or special signals for validating forms. Original functions are left unmodified by the decorators. These decorators instantiate a djangofloor.decorators.Connection object and stores it in the corresponding dict (REGISTERED_FUNCTIONS or REGISTERED_SIGNALS).

Restrict signal/function use

When creating a connection, you provide a callable that checks if the browser is allowed to call this code. By default, the registered code can only be called from Python code. The callable takes three arguments:

Argument validators

The registered Python code can use py3 annotation for specifying data types.

from djangofloor.decorators import Choice, RE, SerializedForm
from django import forms

class MyForm(forms.Form):
    test = forms.CharField()

@signal(path='demo.signal')
def my_signal(window_info, kwarg1: Choice([1, 2], int)=1, kwarg2: Re('^\d+$', int)=2,
        kwarg3: SerializedForm(MyForm)):
   assert isinstance(kwarg1, int)
   assert isinstance(kwarg2, int)
   assert isinstance(kwarg3, MyForm)

scall(window_info, 'demo.signal', to=[SERVER], kwarg1="1", kwarg2="12312", kwarg3=[{'value': '12', 'name': 'test'}])
class djangofloor.decorators.Choice(values, caster=None)[source]

used to check if a value is among some valid choices.

Example (requires Python 3.2+), for a function that can only two values:

@signal(path='myproject.signals.test')
def test(window_info, value: Choice([True, False])):
    pass

Your code wan’t be called if value is not True or False.

Parameters:caster – callable to convert the provided deserialized JSON data before checking its validity.
class djangofloor.decorators.Connection(fn, path=None, is_allowed_to=<function server_side>, queue=None)[source]

Parent class of a registered signal or remote function. Do not use it directly.

check(kwargs)[source]

Check the provided kwargs and apply provided annotations to it. Return None if something is invalid (like an error raised by an annotation or a missing argument).

get_queue(window_info, original_kwargs)[source]

Provide the Celery queue name as a string.

register()[source]

Register the Python code to the right dict.

required_function_arg = 'window_info'
signature_check(fn)[source]

Analyze the signature of the registered Python code, and store the annotations. Check if the first argument is window_info.

class djangofloor.decorators.DynamicQueueName[source]

Allow to dynamically select a Celery queue when the signal is called. You can use it if all signals of a user must be processed by the same worker, but you still

want to dispatch signals to several workers.
get_available_queues()[source]

return the set of all queues that can be returned by the __call__ method. However, if this method is not implemented, the impact is currently limited:

  • the monitoring view will not display all required queues,
  • the systemd service files (provided by the packaging command) will not create all required workers.
class djangofloor.decorators.FormValidator(fn, path=None, is_allowed_to=<function server_side>, queue=None)[source]

Special signal, dedicated to dynamically validate a HTML form.

However, files cannot be sent in the validation process.

signature_check(fn)[source]

override the default method for checking the arguments, since they are independent from the Django Form.

class djangofloor.decorators.FunctionConnection(fn, path=None, is_allowed_to=<function server_side>, queue=None)[source]

represent a WS function

register()[source]

register the WS function into the REGISTERED_FUNCTIONS dict

class djangofloor.decorators.LegacySignalConnection(fn, path=None, is_allowed_to=<function server_side>, queue=None)[source]

Deprecated since version 1.0: do not use it

class djangofloor.decorators.RE(value, caster=None, flags=0)[source]

used to check if a string value matches a given regexp.

Example (requires Python 3.2+), for a function that can only handle a string of the form 123a456:

@signal(path='myproject.signals.test')
def test(window_info, value: RE('\d{3}a\d{3}')):
    pass

Your code won’t be called for values like “abc”.

Parameters:
  • value (str) – regexp pattern
  • caster (callable or None) – if not None, any callable applied to the value (if valid)
  • flags (int) – regexp flags passed to re.compile
class djangofloor.decorators.RandomDynamicQueueName(prefix: str, size: int)[source]

Return a random queue on each signal call.

This class is somewhat useless since you could just run more workers on the same queue.

>>> q = RandomDynamicQueueName('prefix-', 2)
>>> q.get_available_queues() == {'prefix-0', 'prefix-1'}
True
>>> q(None, None, None) in {'prefix-0', 'prefix-1'}
True
get_available_queues()[source]
class djangofloor.decorators.SerializedForm(form_cls)[source]

Transform values sent by JS to a Django form.

Given a form and a list of dict, transforms the list into a django.http.QueryDict and initialize the form with it.

>>> from django import forms
>>> class SimpleForm(forms.Form):
...    field = forms.CharField()
...
>>> x = SerializedForm(SimpleForm)
>>> form = x([{'name': 'field', 'value': 'object'}])
>>> form.is_valid()
True

How to use it with Python3:

@signal(path='myproject.signals.test')
def test(window_info, value: SerializedForm(SimpleForm), other: int):
    print(value.is_valid())

How to use it with Python2:

@signal(path='myproject.signals.test')
def test(window_info, value, other):
    value = SerializedForm(SimpleForm)(value)
    print(value.is_valid())

On the JS side, you can serialize the form with JQuery:

<form onsubmit="return $.df.call('myproject.signals.test', {value: $(this).serializeArray(), other: 42})">
    <input name='field' value='test' type='text'>
</form>
class djangofloor.decorators.SignalConnection(fn, path=None, is_allowed_to=<function server_side>, queue=None)[source]

represents a connected signal.

call(window_info, **kwargs)[source]
register()[source]

register the signal into the REGISTERED_SIGNALS dict

djangofloor.decorators.connect(fn=None, path=None, delayed=False, allow_from_client=True, auth_required=True)[source]

Deprecated since version 1.0: do not use it

djangofloor.decorators.everyone(connection, window_info, kwargs)[source]

allow everyone to call a Python WS signal or remote function

>>> @signal(is_allowed_to=everyone)
>>> def my_signal(request, arg1=None):
>>>     print(request, arg1)
djangofloor.decorators.function(fn=None, path=None, is_allowed_to=<function server_side>, queue=None)[source]

Allow the following Python code to be called from the JavaScript code. The result of this function is serialized (with JSON and settings.WEBSOCKET_SIGNAL_ENCODER) before being sent to the JavaScript part.

from djangofloor.decorators import function, everyone

@function(path='myproject.myfunc', is_allowed_to=everyone)
def myfunc(window_info, arg=None)
    print(arg)
    return 42

The this function can be called from your JavaScript code:

$.dfws.myproject.myfunc({arg: 3123}).then(function(result) { alert(result); });
class djangofloor.decorators.has_perm(perm)[source]

restrict a WS signal or a WS function to users with permission “perm”

>>> @signal(is_allowed_to=has_perm('app_label.codename'))
>>> def my_signal(request, arg1=None):
>>>     print(request, arg1)
djangofloor.decorators.is_anonymous(connection, window_info, kwargs)[source]

restrict a WS signal or a WS function to anonymous users

>>> @signal(is_allowed_to=is_anonymous)
>>> def my_signal(request, arg1=None):
>>>     print(request, arg1)
djangofloor.decorators.is_authenticated(connection, window_info, kwargs)[source]

restrict a WS signal or a WS function to authenticated users

>>> @signal(is_allowed_to=is_authenticated)
>>> def my_signal(request, arg1=None):
>>>     print(request, arg1)
djangofloor.decorators.is_staff(connection, window_info, kwargs)[source]

restrict a WS signal or a WS function to staff users

>>> @signal(is_allowed_to=is_staff)
>>> def my_signal(request, arg1=None):
>>>     print(request, arg1)
djangofloor.decorators.is_superuser(connection, window_info, kwargs)[source]

restrict a WS signal or a WS function to superusers

>>> @signal(is_allowed_to=is_superuser)
>>> def my_signal(request, arg1=None):
>>>     print(request, arg1)
djangofloor.decorators.server_side(connection, window_info, kwargs)[source]

never allows a signal to be called from WebSockets; this signal can only be called from Python code. This is the default choice.

>>> @signal(is_allowed_to=server_side)
>>> def my_signal(window_info, arg1=None):
>>>     print(window_info, arg1)
djangofloor.decorators.signal(fn=None, path=None, is_allowed_to=<function server_side>, queue=None, cls=<class 'djangofloor.decorators.SignalConnection'>)[source]

Decorator to use for registering a new signal. This decorator returns the original callable as-is.

djangofloor.decorators.validate_form(form_cls=None, path=None, is_allowed_to=<function server_side>, queue=None)[source]

Decorator for automatically validating HTML forms. Just add it to your Python code and set the ‘onchange’ attribute to your HTML code. The path argument should be unique to your form class.

param form_cls:any subclass of django.forms.Form
param path:unique name of your form
param is_allowed_to:
 callable for restricting the use of the form validation
param queue:name (or callable) for ensuring small response times
from djangofloor.decorators import everyone, validate_form

@validate_form(path='djangofloor.validate.search', is_allowed_to=everyone, queue='fast')
class MyForm(forms.Form):
    name = forms.CharField()
    ...
<form onchange="$.df.validateForm(this, 'djangofloor.validate.search');"  action="?" method="post">
    {% csrf_token %}
    {% bootstrap_form form %}
    <input type="submit" class="btn btn-primary" value="{% trans 'Search' %}">
</form>

djangofloor.forms

Forms used in default views

Currently, only a SearchForm is provided.

class djangofloor.forms.LogNameForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None)[source]
base_fields = OrderedDict([('log_name', <django.forms.fields.ChoiceField object>), ('other_log_name', <django.forms.fields.CharField object>), ('message', <django.forms.fields.CharField object>), ('level', <django.forms.fields.ChoiceField object>)])
declared_fields = OrderedDict([('log_name', <django.forms.fields.ChoiceField object>), ('other_log_name', <django.forms.fields.CharField object>), ('message', <django.forms.fields.CharField object>), ('level', <django.forms.fields.ChoiceField object>)])
media
class djangofloor.forms.SearchForm(data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None)[source]

Only one query field is provided.

base_fields = OrderedDict([('q', <django.forms.fields.CharField object>)])
declared_fields = OrderedDict([('q', <django.forms.fields.CharField object>)])
media

djangofloor.functions

Module with all default DjangoFloor WS functions

For all app in settings.INSTALLED_APPS, DjangoFloor tries to import app.functions for auto-discovering WS functions. If you want to write your WS functions into other modules, be sure that app.functions imports these modules.

djangofloor.functions.renew_csrf(window_info)[source]
djangofloor.functions.validate_set_password_form(window_info, data=None)[source]

Dynamically validate the SetPasswordForm (for self-modifying its password) class.

$.dfws.df.validate.set_password({data: $(this).serializeArray()}).then(function (r) {console.log(r); })

djangofloor.log

Log utilities to improve Django logging

Define useful handlers, formatters and filters and generate an complete log configuration.

class djangofloor.log.AdminEmailHandler(include_html=False, email_backend=None)[source]

Enhance the AdminEmailHandler provided by Django: Does not try to send email if settings.EMAIL_HOST is not set. Also limits the mail rates to avoid to spam the poor admins.

can_send_email()[source]

Check the time of the previous email to allow the new one

min_interval = 600

min time (in seconds) between two successive sends

send_mail(subject, message, *args, **kwargs)[source]

just check if email can be sent before applying the original method.

class djangofloor.log.ColorizedFormatter(*args, **kwargs)[source]

Used in console for applying colors to log lines, corresponding to the log level.

format(record)[source]

apply a log color, corresponding to the log level

formatStack(stack_info)[source]
class djangofloor.log.LogConfiguration[source]

Generate a log configuration depending on a few parameters:

  • the debug mode (if DEBUG == True, everything is printed to the console and lower log level are applied),
  • the log directory (if set, everything is output to several rotated log files),
  • the log remote URL (to send data to syslog or logd),
  • script name (for determining the log filename).

Required values in the settings_dict:

  • DEBUG: True or False
  • DF_MODULE_NAME: your project name, used to determine log filenames,
  • SCRIPT_NAME: name of the current Python script (“django”, “aiohttp”, “gunicorn” or “celery”)
  • LOG_DIRECTORY: dirname where log files are written (!),
  • LOG_REMOTE_URL: examples: “syslog+tcp://localhost:514/user”, “syslog:///local7”
    “syslog:///dev/log/daemon”, “logd:///project_name”
  • LOG_REMOTE_ACCESS: also send HTTP requests to syslog/journald
  • SERVER_NAME: the public name of the server (like “www.example.com”)
  • SERVER_PORT: the public port (probably 80 or 443)
  • LOG_EXCLUDED_COMMANDS: Django commands that do not write logs
  • RAVEN_DSN: Sentry DSN (URL embedding login and password)
add_handler(logger: str, filename: str, level: str = 'WARN', formatter=None, **kwargs)[source]

Add a handler to a logger. The name of the added handler is unique, so the definition of the handler is also add if required. You can use “ROOT” as logger name to target the root logger.

filename: can be a filename or one of the following special values: “stderr”, “stdout”, “logd”, “syslog”

add_remote_collector(log_remote_url, log_remote_access)[source]
fmt_stderr
fmt_stdout
get_default_filters()[source]
get_default_formatters()[source]
get_default_handlers()[source]
get_default_loggers()[source]
get_default_root()[source]
static get_smart_command_name(module_name, script_name, argv, excluded_commands=None)[source]

Return a “smart” name for the current command line. If it’s an interactive Django command (think to “migrate”), returns None It it’s the celery worker process, specify the running queues in the name Otherwise, add the Django command in the name.

Parameters:
  • module_name
  • script_name
  • argv
  • excluded_commands
Returns:

parse_syslog_url(parsed_log_url, scheme, device, facility_name)[source]
request_loggers = ['aiohttp.access', 'gunicorn.access', 'django.server', 'geventwebsocket.handler']
required_settings = ['DEBUG', 'DF_MODULE_NAME', 'SCRIPT_NAME', 'LOG_DIRECTORY', 'LOG_REMOTE_URL', 'LOG_REMOTE_ACCESS', 'SERVER_NAME', 'SERVER_PORT', 'LOG_EXCLUDED_COMMANDS', 'RAVEN_DSN']
static resolve_command()[source]
class djangofloor.log.PidFilename[source]
static get_command_info(module_name, script_name, argv, excluded_commands=None)[source]

Return a “smart” name for the current command line. If it’s an interactive Django command (think to “migrate”), returns None It it’s the celery worker process, specify the running queues in the name Otherwise, add the Django command in the name.

Parameters:
  • module_name
  • script_name
  • argv
  • excluded_commands
Returns:

required_settings = ['PID_DIRECTORY', 'SCRIPT_NAME', 'LOG_EXCLUDED_COMMANDS', 'DF_MODULE_NAME', 'DEBUG']
class djangofloor.log.RemoveDuplicateWarnings(name='')[source]

Displays py.warnings messages unless the same warning was already sent.

filter(record)[source]

check if the message has already been sent from the same Python file.

class djangofloor.log.ServerFormatter(*args, **kwargs)[source]
format(record)[source]
uses_server_time()[source]

djangofloor.middleware

HttpRequest and WindowInfo middlewares

Two kinds of middlewares are provided:

The DjangoFloorMiddleware provides several things:

  • authenticates users with the settings.DF_REMOTE_USER_HEADER HTTP header,
  • generates a unique ID and add it to the django.http.request.HttpRequest,
  • add the ‘X-UA-Compatible’ header to the response,
  • if settings.DF_FAKE_AUTHENTICATION_USERNAME and the settings.DEBUG are set, a username is set, for simplifying debug sessions,
  • overrides the ‘REMOTE_ADDR’ META attribute since your project is assumed to be run behind a reverse proxy,
  • if the HTTP_AUTHORIZATION header is set, use it for authenticating users (HTTP basic auth)

The class WindowInfoMiddleware allows to:

class djangofloor.middleware.BasicAuthMiddleware(get_response=None)[source]

Deprecated class, replaced by djangofloor.middleware.DjangoFloorMiddleware

process_request(request)[source]
class djangofloor.middleware.BrowserMiddleware[source]

add attributes related to the browser (currently only the HTTP_USER_AGENT header)

from_dict(window_info, values)[source]
from_request(request, window_info)[source]
get_context(window_info)[source]
new_window_info(window_info)[source]
to_dict(window_info)[source]
class djangofloor.middleware.DjangoAuthMiddleware[source]

handle attributes related to the django.contrib.auth framework

from_dict(window_info, values)[source]
from_request(request, window_info)[source]
get_context(window_info)[source]

provide the same context data as the django.contrib.auth.context_processors:

install_methods(window_info_cls)[source]
new_window_info(window_info)[source]
to_dict(window_info)[source]
class djangofloor.middleware.DjangoFloorMiddleware(get_response=None)[source]

Like django.contrib.auth.middleware.RemoteUserMiddleware but:

  • can use any header defined by the setting DF_REMOTE_USER_HEADER,
  • handle the HTTP_X_FORWARDED_FOR HTTP header (set the right client IP)
  • handle HTTP basic authentication
  • set response header for Internet Explorer (to use its most recent render engine)
ajax_header = 'HTTP_WINDOW_KEY'
format_remote_username(remote_username)[source]
header = None
process_request(request: django.http.request.HttpRequest)[source]
process_response(request, response)[source]
remote_user_authentication(request, username)[source]
class djangofloor.middleware.Djangoi18nMiddleware[source]

Add attributes required for using i18n-related functions.

from_dict(window_info, values)[source]
from_request(request, window_info)[source]
get_context(window_info)[source]
new_window_info(window_info)[source]
to_dict(window_info)[source]
class djangofloor.middleware.FakeAuthenticationMiddleware(get_response=None)[source]

Deprecated class, replaced by djangofloor.middleware.DjangoFloorMiddleware

group_cache = {}
process_request(request)[source]
class djangofloor.middleware.IEMiddleware(get_response=None)[source]

Deprecated since version 1.0: replaced by djangofloor.middleware.DjangoFloorMiddleware

process_request(request)[source]
process_template_response(request, response)[source]
class djangofloor.middleware.RemoteUserMiddleware(get_response=None)[source]

Deprecated class, replaced by djangofloor.middleware.DjangoFloorMiddleware

header = None
original_process_request(request)[source]
process_request(request)[source]
class djangofloor.middleware.WindowInfoMiddleware[source]

Base class for the WindowInfo middlewares.

from_dict(window_info, values)[source]
from_request(request, window_info)[source]
get_context(window_info)[source]
install_methods(window_info_cls)[source]
new_window_info(window_info)[source]
to_dict(window_info)[source]
class djangofloor.middleware.WindowKeyMiddleware[source]

handle the unique ID generated for each django.http.request.HttpRequest and copy it to the WindowInfo object

from_dict(window_info, values)[source]
from_request(request, window_info)[source]
get_context(window_info)[source]
new_window_info(window_info)[source]
to_dict(window_info)[source]

djangofloor.models

Django models specific to DjangoFloor

Currently, only defines models for Notification:

  • Notifications themselves,
  • NotificationRead, that tracks traces of read actions from users.

Non-authenticated users uses sessions for tracking read actions.

class djangofloor.models.Notification(id, content, title, icon, is_active, not_before, not_after, level, auto_hide_seconds, display_mode, broadcast_mode, repeat_count)[source]
ANY = 0
AUTHENTICATED = 1
BANNER = 'banner'
DANGER = 'danger'
exception DoesNotExist
INFO = 'info'
MODAL = 'modal'
exception MultipleObjectsReturned
NOTIFICATION = 'notification'
POPUP = 'popup'
SELECTED_GROUPS_OR_USERS = 2
SUCCESS = 'success'
SYSTEM = 'system'
WARNING = 'warning'
author

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

pizza.toppings and topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

auto_hide_seconds

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

broadcast_mode

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

content

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

destination_groups

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

pizza.toppings and topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

destination_users

Accessor to the related objects manager on the forward and reverse sides of a many-to-many relation.

In the example:

class Pizza(Model):
    toppings = ManyToManyField(Topping, related_name='pizzas')

pizza.toppings and topping.pizzas are ManyToManyDescriptor instances.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

display_mode

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

get_broadcast_mode_display(*moreargs, **morekwargs)
get_display_mode_display(*moreargs, **morekwargs)
get_level_display(*moreargs, **morekwargs)
classmethod get_notifications(request)[source]
icon

The descriptor for the file attribute on the model instance. Returns a FieldFile when accessed so you can do stuff like:

>>> from myapp.models import MyModel
>>> instance = MyModel.objects.get(pk=1)
>>> instance.file.size

Assigns a file object on assignment so you can do:

>>> with open('/path/to/hello.world', 'r') as f:
...     instance.file = File(f)
id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

is_active

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

level

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

not_after

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

not_before

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

notificationread_set

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

objects = <django.db.models.manager.Manager object>
repeat_count

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

title

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

class djangofloor.models.NotificationRead(id, notification, user, first_read_time, last_read_time, read_count)[source]
exception DoesNotExist
exception MultipleObjectsReturned
first_read_time

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

get_next_by_first_read_time(*moreargs, **morekwargs)
get_next_by_last_read_time(*moreargs, **morekwargs)
get_previous_by_first_read_time(*moreargs, **morekwargs)
get_previous_by_last_read_time(*moreargs, **morekwargs)
id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

last_read_time

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

notification

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

child.parent is a ForwardManyToOneDescriptor instance.

notification_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

objects = <django.db.models.manager.Manager object>
read_count

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

user

Accessor to the related object on the forward side of a many-to-one or one-to-one (via ForwardOneToOneDescriptor subclass) relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

child.parent is a ForwardManyToOneDescriptor instance.

user_id

A wrapper for a deferred-loading field. When the value is read from this object the first time, the query is executed.

djangofloor.models.apply_post_migrate_settings(sender, **kwargs)[source]

Defined for calling “post_migrate” method on each ConfigValue setting through the “post_migrate” Django signal

djangofloor.models.apply_pre_migrate_settings(sender, **kwargs)[source]

Defined for calling “pre_migrate” method on each ConfigValue setting through the “pre_migrate” Django signal.

djangofloor.models.apply_social_auth_configurations(sender, *args, **kwargs)[source]

create social apps in database, with data from the config file

djangofloor.root_urls

Root URLs provided by DjangoFloor

By default, register URLs for the admin site, jsi18n, static and media files, favicon and robots.txt. If DjangoDebugToolbar is present, then its URL is also registered.

djangofloor.scripts

“Main” functions for Django, Celery, Gunicorn and uWSGI

Define “main” functions for your scripts using the Django manage.py system or Gunicorn/Celery/uWSGI.

class djangofloor.scripts.CeleryCommand[source]
add_arguments(parser: argparse.ArgumentParser)[source]
run_script()[source]
class djangofloor.scripts.DjangoCommand[source]

Main function, calling Django code for management commands. Retrieve some default values from Django settings.

commands = None
run_script()[source]
class djangofloor.scripts.GunicornCommand[source]

wrapper around gunicorn. Retrieve some default values from Django settings.

Returns:
add_arguments(parser: argparse.ArgumentParser)[source]
run_script()[source]
class djangofloor.scripts.ScriptCommand[source]
add_argument(parser, *args, **kwargs)[source]
add_arguments(parser: argparse.ArgumentParser)[source]
run_script()[source]
set_options(options)[source]
djangofloor.scripts.control()[source]

A single command to rule them all… Replace django, gunicorn/aiohttp and celery commands. “myproject-ctl” command

“worker” -> changed as “myproject-celery” “worker” “server” -> changed as “myproject-aiohttp” “celery” -> changed as “myproject-celery” command other value -> changed as “myproject-django” command

djangofloor.scripts.create_project()[source]
djangofloor.scripts.get_application(command_name: typing.Union[str, NoneType] = None, script_name: typing.Union[str, NoneType] = None)[source]
djangofloor.scripts.get_merger_from_env() → djangofloor.conf.merger.SettingMerger[source]

Should be used after set_env(); determine all available settings in this order:

  • djangofloor.defaults
  • {project_name}.defaults (overrides djangofloor.defaults)
  • {root}/etc/{project_name}/settings.ini (overrides {project_name}.settings)
  • {root}/etc/{project_name}/settings.py (overrides {root}/etc/{project_name}/settings.ini)
  • ./local_settings.ini (overrides {root}/etc/{project_name}/settings.py)
  • ./local_settings.py (overrides ./local_settings.ini)
djangofloor.scripts.load_celery()[source]

Import Celery application unless Celery is disabled. Allow to automatically load tasks

djangofloor.scripts.set_default_option(options, name: str)[source]
djangofloor.scripts.set_env(command_name: typing.Union[str, NoneType] = None, script_name: typing.Union[str, NoneType] = None)[source]

Set the environment variable DF_CONF_NAME with the project name and the script name The value looks like “project_name:celery” or “project_name:django”

determine the project name

if the script is {xxx}-[gunicorn|manage][.py], then the project_name is assumed to be {xxx} if option –dfproject {xxx} is available, then the project_name is assumed to be {xxx}
djangofloor.scripts.set_management_get_commands[source]
djangofloor.scripts.uwsgi()[source]

djangofloor.signals

Module with all default DjangoFloor signals

For all app in settings.INSTALLED_APPS, DjangoFloor tries to import app.signals for auto-discovering signals. If you want to write your signals into other modules, be sure that app.signals imports these modules.

djangofloor.signals.check_websockets(window_info)[source]

Check what happens when an exception is raised in a Celery queue

djangofloor.signals.generate_log(window_info, form: <djangofloor.decorators.SerializedForm object at 0x7f271ab20b38>)[source]

Used for checking if websockets are functional or not for your installation. Called from the monitoring view.

djangofloor.signals.bootstrap3

Python shortcuts for JS Bootstrap3 signals

This module only defines shortcuts to existing JS signals that are linked to Boostrap3.

djangofloor.signals.bootstrap3.modal_hide(window_info, to=WINDOW)[source]
djangofloor.signals.bootstrap3.modal_show(window_info, html_content, width=None, to=WINDOW)[source]
djangofloor.signals.bootstrap3.notify(window_info, content, title=None, timeout=5000, style='notification', level='info', to=WINDOW)[source]

Display a notification to the selected users. Can be a banner (on the top of the page), a Growl-like notification (bubble on the top-right), a modal or a system notification.

Parameters:
  • window_info – a djangofloor.wsgi.window_info.WindowInfo object
  • content – content of the notification
  • title – title of the notification
  • timeout – number of milliseconds before hiding the message
  • style – one of djangofloor.signals.bootstrap3.{NOTIFICATION,BANNER,MODAL,SYSTEM}
  • level – one of djangofloor.signals.bootstrap3.{INFO,DEFAULT,SUCCESS,DANGER,WARNING}
  • to – list of signal clients
djangofloor.signals.bootstrap3.render_to_modal(window_info, template_name, context, width=None, to=WINDOW, using=None)[source]

render a template and display the result in a modal window on every selected clients

Parameters:
  • window_info – a djangofloor.wsgi.window_info.WindowInfo object
  • template_name – name of the Django template
  • context – template context (a dict)
  • width – width of the modal, in pixels
  • to – list of signal clients
  • using – the using parameter of Django templates

djangofloor.signals.html

Python shortcuts for JS signals

This module only defines shortcuts to existing JS signals. These signals are intended to be called on the JS side from the Python side and allow you to easily modify the client webpage.

djangofloor.signals.html.add(window_info, selector, html_content, to=WINDOW)[source]

Create a new JS object (with jQuery) with elements added to the set of matched elements.

djangofloor.signals.html.add_attribute(window_info, selector, attr_name, attr_value, to=WINDOW)[source]

Adds the specified attribute(s) to each of the set of matched elements.

djangofloor.signals.html.add_class(window_info, selector, class_name, to=WINDOW)[source]

Adds the specified class(es) to each of the set of matched elements.

djangofloor.signals.html.after(window_info, selector, html_content, to=WINDOW)[source]

Insert content, specified by the parameter, before after element in the set of matched elements.

djangofloor.signals.html.append(window_info, selector, html_content, to=WINDOW)[source]

Insert content, specified by the parameter, to the end of each element in the set of matched elements.

djangofloor.signals.html.before(window_info, selector, html_content, to=WINDOW)[source]

Insert content, specified by the parameter, before each element in the set of matched elements.

djangofloor.signals.html.content(window_info, selector, html_content, to=WINDOW)[source]

set the HTML contents of every matched element.

Parameters:
  • window_info – a djangofloor.wsgi.window_info.WindowInfo object
  • html_content – HTML content sent to the client
  • selector – jQuery selector (like “#my_div”)
  • to – list of signal clients
djangofloor.signals.html.download_file(window_info, url, filename=None, to=WINDOW)[source]

Force the client to download the given file.

djangofloor.signals.html.empty(window_info, selector, to=WINDOW)[source]

Remove all child nodes of the set of matched elements from the DOM.

djangofloor.signals.html.fade_in(window_info, selector, duration=400, to=WINDOW)[source]

Display the matched elements by fading them to opaque.

djangofloor.signals.html.fade_out(window_info, selector, duration=400, to=WINDOW)[source]

Hide the matched elements by fading them to transparent.

djangofloor.signals.html.fade_to(window_info, selector, opacity, duration=400, to=WINDOW)[source]

Adjust the opacity of the matched elements.

Parameters:
  • window_infodjangofloor.wsgi.window_info.WindowInfo
  • selector – jQuery selector
  • duration – determining how long the animation will run (ms).
  • opacity – A number between 0 and 1 denoting the target opacity.
  • to – target of the signal
djangofloor.signals.html.fade_toggle(window_info, selector, duration=400, easing='swing', to=WINDOW)[source]

Display or hide the matched elements by animating their opacity.

Parameters:
  • window_infodjangofloor.wsgi.window_info.WindowInfo
  • selector – jQuery selector
  • duration – determining how long the animation will run (ms).
  • easing – A string indicating which easing function to use for the transition.
  • to – target of the signal
djangofloor.signals.html.focus(window_info, selector, to=WINDOW)[source]

Set the focus to the matched element.

djangofloor.signals.html.hide(window_info, selector, duration=400, easing='swing', to=WINDOW)[source]

Hide the matched elements.

djangofloor.signals.html.prepend(window_info, selector, html_content, to=WINDOW)[source]

Insert content, specified by the parameter, to the beginning of each element in the set of matched elements.

djangofloor.signals.html.remove(window_info, selector, to=WINDOW)[source]

Remove the set of matched elements from the DOM.

djangofloor.signals.html.remove_attr(window_info, selector, attr_name, to=WINDOW)[source]

Remove an attribute from each element in the set of matched elements.

djangofloor.signals.html.remove_class(window_info, selector, class_name, to=WINDOW)[source]

Remove a single class, multiple classes, or all classes from each element in the set of matched elements.

djangofloor.signals.html.render_to_client(window_info, template_name, context, selector, to=WINDOW, using=None)[source]

render a template and send the result inside a HTML selector on every selected clients

Parameters:
  • window_info – a djangofloor.wsgi.window_info.WindowInfo object
  • template_name – name of the Django template
  • context – template context (a dict)
  • selector – jQuery selector (like “#my_div”)
  • to – list of signal clients
  • using – the using parameter of Django templates
djangofloor.signals.html.replace_with(window_info, selector, html_content, to=WINDOW)[source]

Replace each element in the set of matched elements with the provided new content.

djangofloor.signals.html.show(window_info, selector, duration=400, easing='swing', to=WINDOW)[source]

Display the matched elements.

djangofloor.signals.html.text(window_info, selector, text_content, to=WINDOW)[source]

Set the text contents of the matched elements.

djangofloor.signals.html.toggle(window_info, selector, duration=400, easing='swing', to=WINDOW)[source]

Display or hide the matched elements.

djangofloor.signals.html.trigger(window_info, selector, event, to=WINDOW)[source]

Execute all handlers and behaviors attached to the matched elements for the given event type.

djangofloor.signals.html.val(window_info, selector, value, to=WINDOW)[source]

Set the value of every matched element.

djangofloor.tasks

Define Celery tasks and functions for calling signals

This module is automatically imported by Celery. Use these functions for:

class djangofloor.tasks.Constant(name)[source]

Allow to define constants that can be nicely printed to stdout

djangofloor.tasks.call(window_info, signal_name, to=None, kwargs=None, countdown=None, expires=None, eta=None)[source]

Call a DjangoFloor signal.

Parameters:
  • window_info – either a django.http.request.HttpRequest or a djangofloor.wsgi.window_info.WindowInfo
  • signal_name – name of the called signal (str)
  • tolist of the topics that should receive the signal
  • kwargs – dict with all arguments of your signal. Will be encoded to JSON with settings.WEBSOCKET_SIGNAL_ENCODER and decoded with settings.WEBSOCKET_SIGNAL_DECODER.
  • countdown – check the Celery doc (in a nutshell: number of seconds before executing the signal)
  • expires – check the Celery doc (in a nutshell: if this signal is not executed before this number of seconds, it is cancelled)
  • eta – check the Celery doc (in a nutshell: datetime of running this signal)
djangofloor.tasks.df_call(signal_name, request, sharing=None, from_client=False, kwargs=None, countdown=None, expires=None, eta=None)[source]

Deprecated since version 1.0,: do not use it

djangofloor.tasks.get_expected_queues()[source]
djangofloor.tasks.get_websocket_redis_connection()[source]

Return a valid Redis connection, using a connection pool.

djangofloor.tasks.import_signals()[source]

Deprecated since version 1.0: do not use it

djangofloor.tasks.import_signals_and_functions[source]

Import all signals.py, ‘forms.py’ and functions.py files to register signals and WS functions (tries these files for all Django apps).

djangofloor.tasks.scall(window_info, signal_name, to=None, **kwargs)[source]

Shortcut to djangofloor.tasks.call(), allowing to directly pass arguments of the signal to this function. Your signal cannot use window_info, signal_name and to as argument names.

These two successive calls are strictly equivalent:

from djangofloor.tasks import call, scall, WINDOW, SERVER

def my_python_view(request):
    scall(request, 'my.signal.name', to=[WINDOW, SERVER], arg1=12, arg2='Hello')
    call(request, 'my.signal.name', to=[WINDOW, SERVER], kwargs={'arg1': 12, 'arg2': 'Hello'})
djangofloor.tasks.set_websocket_topics(request, *topics)[source]

Use it in a Django view for setting websocket topics. Any signal sent to one of these topics will be received by the client.

param request:django.http.request.HttpRequest
param topics:list of topics that will be subscribed by the websocket (can be any Python object).

djangofloor.templatetags.djangofloor

Templatetags for media files, websockets, notifications

Various templatetags provided by DjangoFloor.

djangofloor.templatetags.djangofloor.df_deprecation(value)[source]

Deprecated since version 1.0: do not use it

djangofloor.templatetags.djangofloor.df_init_websocket(context, *topics)[source]

Set the websocket URL with the access token. Allow to access the right topics.

djangofloor.templatetags.djangofloor.df_inivalue(value)[source]

Deprecated since version 1.0: do not use it

djangofloor.templatetags.djangofloor.df_level(value, bounds='80:95')[source]

Convert a numeric value to “success”, “warning” or “danger”. The two required bounds are given by a string.

djangofloor.templatetags.djangofloor.df_messages(context, style='banner')[source]

Show django.contrib.messages Messages in Metro alert containers. In order to make the alerts dismissable (with the close button), we have to set the jquery parameter too when using the bootstrap_javascript tag. Uses the template bootstrap3/messages.html.

Parameters:
  • context – current template context
  • style – “notification”, “banner”, “modal” or “system”

Example for your template:

{% df_messages style='banner' %}
{% df_messages style='notification' %}
djangofloor.templatetags.djangofloor.df_window_key(context)[source]

Deprecated since version 1.0: do not use it

djangofloor.templatetags.djangofloor.django_form(form)[source]
djangofloor.templatetags.djangofloor.django_icon(name, color=None)[source]

Add font-awesome icons in your HTML code

djangofloor.templatetags.djangofloor.do_media(parser, token)[source]

Joins the given path with the MEDIA_URL setting.

Usage:

{% media path [as varname] %}

Examples:

.. code-block:: html
{% media “myapp/css/base.css” %} {% media variable_with_path %} {% media “myapp/css/base.css” as admin_base_css %} {% media variable_with_path as varname %}
djangofloor.templatetags.djangofloor.fontawesome_icon(name, prefix='fa', large=False, fixed=False, spin=False, li=False, rotate=None, border=False, color=None)[source]

Add font-awesome icons in your HTML code

djangofloor.templatetags.djangofloor.media(path)[source]

If you want to reuse the template tag in your Python code.

djangofloor.templatetags.pipeline

Emulate django-pipeline templatetags

Allows you to use the same javascript and stylesheet template tags if django-pipeline is not installed. If you add django-pipeline to your settings.INSTALLED_APPS, these versions are ignored, using the original ones. If you keep the default settings, django-pipeline is automatically detected and added, so you have nothing to do.

class djangofloor.templatetags.pipeline.PyScssCompiler[source]

SASS (.scss) compiler based on the Python library pyScss. (http://pyscss.readthedocs.io/en/latest/ ). However, this compiler is limited to SASS 3.2 and cannot compile modern projets like Bootstrap 4. Please use pipeline.compilers.sass.SASSCompiler if you use modern SCSS files.

compile_file(infile, outfile, outdated=False, force=False)[source]
match_file(filename)[source]
output_extension = 'css'
class djangofloor.templatetags.pipeline.RcssCompressor[source]

JS compressor based on the Python library slimit (http://pypi.python.org/pypi/slimit/ ).

compress_css(css)[source]
class djangofloor.templatetags.pipeline.TypescriptCompiler[source]

TypeScript (.ts) compiler using “tsc”. (https://www.typescriptlang.org ).

compile_file(infile, outfile, outdated=False, force=False)[source]
match_file(filename)[source]
output_extension = 'js'
djangofloor.templatetags.pipeline.javascript(key)[source]

insert all javascript files corresponding to the given key

djangofloor.templatetags.pipeline.stylesheet(key)[source]

insert all javascript files corresponding to the given key

djangofloor.tests

class djangofloor.tests.SignalQueue(immediate_execution: bool = False)[source]

Allow to keep called signals in memory instead of actualling sending them to Redis, for testing purposes.

>>> from djangofloor.tasks import scall, SERVER
>>> from djangofloor.wsgi.window_info import WindowInfo
>>> from djangofloor.wsgi.topics import serialize_topic
>>> from djangofloor.decorators import signal
>>> # noinspection PyUnusedLocal
>>> @signal(path='test.signal', queue='demo-queue')
>>> def test_signal(window_info, value=None):
>>>     print(value)
>>>
>>> wi = WindowInfo()
>>> with SignalQueue() as fd:
>>>     scall(wi, 'test.signal', to=[SERVER, 1], value="test value")
>>> 'demo-queue' in fd.python_signals
True
>>> serialize_topic(wi, 1) in fd.ws_signals
True
>>> fd.ws_signals[serialize_topic(wi, 1)]
('test.signal', {'value': 'test value'})
>>> fd.execute_delayed_signals()
'test value'

Of course, you should use this example from a method of a django.test.TestCase.

activate()[source]

replace private function that push signal calls to Celery or Websockets

deactivate()[source]

replace the normal private functions for calling Celery or websockets signals

execute_delayed_signals(queues=None)[source]

execute the Celery signals

djangofloor.urls

URLs specific to DjangoFloor

Define URLs for user authentication forms and for defining JS signals. Also define the URL linked to the monitoring and to the search views.

djangofloor.utils

Utility functions

Define some utility functions like warning or walking through modules.

exception djangofloor.utils.RemovedInDjangoFloor110Warning[source]

Used for displaying functions or modules that will be removed in a near future.

exception djangofloor.utils.RemovedInDjangoFloor200Warning[source]

Used for displaying functions or modules that will be removed in a near future.

djangofloor.utils.ensure_dir(path, parent=True)[source]

Ensure that the given directory exists

Parameters:
  • path – the path to check
  • parent – only ensure the existence of the parent directory
djangofloor.utils.get_view_from_string(view_as_str)[source]
djangofloor.utils.guess_version(defined_settings)[source]

Guesss the project version. Expect an installed version (findable with pkg_resources) or __version__ in your_project/__init__.py. If not found

Parameters:defined_settings (dict) – all already defined settings (dict)
Returns:should be something like “1.2.3”
Return type:str
djangofloor.utils.is_package_present(package_name)[source]

Return True is the package_name package is presend in your current Python environment.

djangofloor.utils.remove_arguments_from_help(parser, arguments)[source]
djangofloor.utils.smart_pipfile_url(url: str) → str[source]

Given a pip install URL, return a valid PipFile line.

>>> smart_pipfile_url('git://git.myproject.org/MyProject#egg=MyProject')
"MyProject = { git = 'git://git.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('git+http://git.myproject.org/MyProject#egg=MyProject')
"MyProject = { git = 'http://git.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('git+https://git.myproject.org/MyProject#egg=MyProject')
"MyProject = { git = 'https://git.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('git+ssh://git.myproject.org/MyProject#egg=MyProject')
"MyProject = { git = 'ssh://git.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('git+git://git.myproject.org/MyProject#egg=MyProject')
"MyProject = { git = 'git://git.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('git+file://git.myproject.org/MyProject#egg=MyProject')
"MyProject = { git = 'file://git.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('git+git@git.myproject.org:MyProject#egg=MyProject')
"MyProject = { git = 'git://git@git.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('git://git.myproject.org/MyProject.git@master#egg=MyProject')
"MyProject = { git = 'git://git.myproject.org/MyProject.git', ref = 'master', editable = true }"
>>> smart_pipfile_url('git://git.myproject.org/MyProject.git@v1.0#egg=MyProject')
"MyProject = { git = 'git://git.myproject.org/MyProject.git', ref = 'v1.0', editable = true }"
>>> smart_pipfile_url('git://git.myproject.org/MyProject.git@da39a3ee5e6b4b0d3255bfef#egg=MyProject')
"MyProject = { git = 'git://git.myproject.org/MyProject.git', ref = 'da39a3ee5e6b4b0d3255bfef', editable = true }"
>>> smart_pipfile_url('hg+http://hg.myproject.org/MyProject#egg=MyProject')
"MyProject = { hg = 'http://hg.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('hg+https://hg.myproject.org/MyProject#egg=MyProject')
"MyProject = { hg = 'https://hg.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('hg+ssh://hg.myproject.org/MyProject#egg=MyProject')
"MyProject = { hg = 'ssh://hg.myproject.org/MyProject', editable = true }"
>>> smart_pipfile_url('hg+http://hg.myproject.org/MyProject@da39a3ee5e6b#egg=MyProject')
"MyProject = { hg = 'http://hg.myproject.org/MyProject', ref = 'da39a3ee5e6b', editable = true }"
>>> smart_pipfile_url('svn+svn://svn.myproject.org/svn/MyProject#egg=MyProject')
"MyProject = { svn = 'svn://svn.myproject.org/svn/MyProject', editable = true }"
>>> smart_pipfile_url('svn+http://svn.myproject.org/svn/MyProject/trunk@2019#egg=MyProject')
"MyProject = { svn = 'http://svn.myproject.org/svn/MyProject/trunk', ref = '2019', editable = true }"
>>> smart_pipfile_url('bzr+http://bzr.myproject.org/MyProject/trunk#egg=MyProject')
"MyProject = { bzr = 'http://bzr.myproject.org/MyProject/trunk', editable = true }"
>>> smart_pipfile_url('bzr+sftp://user@myproject.org/MyProject/trunk#egg=MyProject')
"MyProject = { bzr = 'sftp://user@myproject.org/MyProject/trunk', editable = true }"
>>> smart_pipfile_url('bzr+ssh://user@myproject.org/MyProject/trunk#egg=MyProject')
"MyProject = { bzr = 'ssh://user@myproject.org/MyProject/trunk', editable = true }"
>>> smart_pipfile_url('requests[socks]')
"requests = { extras = ['socks'] }"
>>> smart_pipfile_url('requests[socks,all]')
"requests = { extras = ['socks', 'all'] }"
>>> smart_pipfile_url('records>0.5.0')
"records = '>0.5.0'"
>>> smart_pipfile_url('git+https://github.com/django/django.git@1.11.4')
"django = { git = 'https://github.com/django/django.git', ref = '1.11.4', editable = true }"
>>> smart_pipfile_url('https://github.com/divio/django-cms/archive/release/3.4.x.zip')
'"7377c666" = { file = \'https://github.com/divio/django-cms/archive/release/3.4.x.zip\' }'
>>> smart_pipfile_url('pywinusb;python_version<"2.7"')
"pywinusb = '*'"
djangofloor.utils.walk(module_name, dirname, topdown=True)[source]

Copy of os.walk(), please refer to its doc. The only difference is that we walk in a package_resource instead of a plain directory. :type module_name: basestring :param module_name: module to search in :type dirname: basestring :param dirname: base directory :type topdown: bool :param topdown: if True, perform a topdown search.

djangofloor.views

DjangoFloor utility views

Also define two functions:

  • read_file_in_chunks(): generate an iterator that reads a file object in chunks
  • send_file(): return an efficient django.http.response.HttpResponse for reading files
class djangofloor.views.IndexView(**kwargs)[source]

index view using the default bootstrap3 view. You can only override the default template or populate the context.

get(request, *args, **kwargs)[source]

single defined method

get_context(request)[source]

provide the template context

template_name = 'djangofloor/bootstrap3/index.html'
djangofloor.views.favicon(request)[source]

Redirect “/favicon.ico” to the favicon in the static files

djangofloor.views.index(request)[source]

Deprecated since version 1.0.

djangofloor.views.read_file_in_chunks(fileobj, chunk_size=32768)[source]

read a file object in chunks of the given size.

Return an iterator of data

Parameters:
  • fileobj
  • chunk_size (int) – max size of each chunk
djangofloor.views.robots(request)[source]

Basic robots file

djangofloor.views.send_file(filepath, mimetype=None, force_download=False)[source]

Send a local file. This is not a Django view, but a function that is called at the end of a view.

If settings.USE_X_SEND_FILE (mod_xsendfile is a mod of Apache), then return an empty HttpResponse with the correct header. The file is directly handled by Apache instead of Python. If settings.X_ACCEL_REDIRECT_ARCHIVE is defined (as a list of tuple (directory, alias_url)) and filepath is in one of the directories, return an empty HttpResponse with the correct header. This is only available with Nginx.

Otherwise, return a StreamingHttpResponse to avoid loading the whole file in memory.

Parameters:
  • filepath – absolute path of the file to send to the client.
  • mimetype – MIME type of the file (returned in the response header)
  • force_download – always force the client to download the file.
Return type:

django.http.response.StreamingHttpResponse or django.http.response.HttpResponse

djangofloor.views.signals(request)[source]

Generate a JS file with the list of signals. Also configure jQuery with a CSRF header for AJAX requests.

djangofloor.views.auth

djangofloor.views.monitoring

Display some system info

A class-based monitoring view, allowing to display some info.

Also define several widgets (MonitoringCheck) that compose this view. You should install the psutil module to add server info (like the CPU usage).

class djangofloor.views.monitoring.AuthenticationCheck[source]

Presents all activated authentication methods

get_context(request)[source]
template = 'djangofloor/django/monitoring/authentication.html'
class djangofloor.views.monitoring.CeleryStats[source]
get_context(request)[source]
template = 'djangofloor/django/monitoring/celery_stats.html'
class djangofloor.views.monitoring.LogAndExceptionCheck[source]
get_context(request)[source]
template = 'djangofloor/django/monitoring/errors.html'
class djangofloor.views.monitoring.LogLastLines[source]
get_context(request)[source]
static get_log_filenames()[source]

Return the list of filenames used in all logging.FileHandler.

template = 'djangofloor/django/monitoring/log_last_lines.html'
class djangofloor.views.monitoring.MonitoringCheck[source]

Base widget of the monitoring view.

check_commandline()[source]
frequency = None

update frequency (currently unused).

get_context(request)[source]

provide the context required to render the widget

render(request)[source]

render the widget as HTML

template = None

name of the template used by this widget

class djangofloor.views.monitoring.Packages[source]

Check a list of given packages given by settings.DF_CHECKED_REQUIREMENTS. Each element is a requirement as listed by pip freeze.

get_context(request)[source]

provide the installed distributions to the template

static get_installed_distributions(raw_installed_distributions, raw_requirements)[source]

return a list of lists, each sublist having 6 elements:

  • the name of the package,
  • the installed version (or None if not installed),
  • the state (“danger”/”warning”/”success”) as a CSS class,
  • the icon (“remove”/”ok”/”warning-sign”)
  • list of specs as strings [‘>= 1’, ‘< 1.8.1’]
  • list of parse_requirements(r) for the package
template = 'djangofloor/django/monitoring/packages.html'

base template

class djangofloor.views.monitoring.RequestCheck[source]
check_commandline()[source]
common_headers = {'HTTP_X_CSRF_TOKEN': 'Used to prevent cross-site request forgery.', 'HTTP_HOST': 'The domain name of the server (for virtual hosting), and the TCP port number.', 'HTTP_EXPECT': 'Indicates that particular server behaviors are required by the client.', 'HTTP_IF_MODIFIED_SINCE': 'Allows a 304 Not Modified to be returned if content is unchanged.', 'HTTP_AUTHORIZATION': 'Authentication credentials for HTTP authentication.', 'HTTP_ACCEPT_ENCODING': 'List of acceptable encodings. See HTTP compression.', 'HTTP_PROXY_CONNECTION': 'Implemented as a misunderstanding of the HTTP specifications.', 'HTTP_USER_AGENT': 'The user agent string of the user agent.', 'HTTP_IF_MATCH': 'Only perform the action if the client supplied entity matches the same entity on the server.', 'HTTP_REFERER': 'This is the address of the previous web page.', 'HTTP_CONTENT_MD5': 'A Base64-encoded binary MD5 sum of the content of the request body.', 'HTTP_CONTENT_TYPE': 'The Media type of the body of the request (used with POST and PUT requests).', 'HTTP_ACCEPT_LANGUAGE': 'List of acceptable human languages for response.', 'HTTP_CONNECTION': 'Control options for the current connection and list of hop-by-hop request fields.', 'HTTP_PRAGMA': 'Implementation-specific fields that may have various effects.', 'HTTP_TE': 'The transfer encodings the user agent is willing to accept.', 'HTTP_PROXY_AUTHORIZATION': 'Authorization credentials for connecting to a proxy.', 'HTTP_DATE': 'The date and time that the message was originated', 'HTTP_IF_UNMODIFIED_SINCE': 'Only send the response if the entity has not been modified since a specific time.', 'HTTP_MAX_FORWARDS': 'Limit the number of times the message can be forwarded through proxies or gateways.', 'HTTP_ORIGIN': 'Initiates a request for cross-origin resource sharing.', 'HTTP_X_FORWARDED_FOR': 'A de facto standard for identifying the originating IP address.', 'HTTP_ACCEPT': 'Media type(s) that is(/are) acceptable for the response.', 'HTTP_DNT': 'Requests a web application to disable their tracking of a user.', 'HTTP_UPGRADE': 'Ask the server to upgrade to another protocol.', 'HTTP_WARNING': 'A general warning about possible problems with the entity body.', 'HTTP_X_FORWARDED_HOST': 'A de facto standard for identifying the original host.', 'HTTP_RANGE': 'Request only part of an entity.', 'HTTP_X_FORWARDED_PROTO': 'A de facto standard for identifying the originating protocol', 'HTTP_X_REQUEST_ID': 'Correlates HTTP requests between a client and server.', 'HTTP_VIA': 'Informs the server of proxies through which the request was sent.', 'HTTP_FROM': 'The email address of the user making the request.', 'HTTP_CACHE_CONTROL': 'Used to specify directives that must be obeyed by caching mechanisms.', 'HTTP_FORWARDED': 'Disclose original information of a client connecting to a web server through an HTTP proxy.', 'HTTP_X_CORRELATION_ID': 'Correlates HTTP requests between a client and server.', 'HTTP_ACCEPT_CHARSET': 'Character sets that are acceptable.', 'HTTP_ACCEPT_DATETIME': 'Acceptable version in time.', 'HTTP_COOKIE': 'An HTTP cookie previously sent by the server with Set-Cookie (below).', 'HTTP_CONTENT_LENGTH': 'The length of the request body in octets (8-bit bytes).', 'HTTP_X_REQUESTED_WITH': 'Mainly used to identify Ajax requests.', 'HTTP_IF_RANGE': 'If the entity is unchanged, send me the part(s) that I am missing or send me the entity.', 'HTTP_FRONT_END_HTTPS': 'Non-standard header field used by Microsoft applications', 'HTTP_IF_NONE_MATCH': 'Allows a 304 Not Modified to be returned if content is unchanged, see HTTP ETag.'}
get_context(request)[source]
template = 'djangofloor/django/monitoring/request_check.html'
class djangofloor.views.monitoring.System[source]
static analyse_pid_file(processes, data)[source]
check_commandline()[source]
static check_pid(pid: str)[source]
excluded_mountpoints = {'/dev'}
get_context(request)[source]
static get_expected_processes()[source]
static read_pid_file(filename)[source]
template = 'djangofloor/django/monitoring/system.html'
djangofloor.views.monitoring.generate_log(request)[source]
djangofloor.views.monitoring.get_installed_distributions()[source]

Return a list of installed Distribution objects. Simplified version of the version provided by pip.

djangofloor.views.monitoring.raise_exception(request)[source]
djangofloor.views.monitoring.system_state(request)[source]

djangofloor.views.search

Abstract global-site search view

The DjangoFloor base template provides a site-wide search field. The corresponding search view is defined by the setting DF_SITE_SEARCH_VIEW and should be a class-based view. Here is an example of abstract class-based view, as well as a generic model search view and an example of working search view (searching across users)

class djangofloor.views.search.ModelSearchView(**kwargs)[source]

Reusable search view that search through a Django model

format_result(obj)[source]

basic format for objects

get_query(request, pattern)[source]

compute the query based on the provided pattern and searched attributes

model

searched model

alias of User

searched_attributes = []

all attributes that are compared to the pattern

sort_attributes = []

if provided, results are ordered by these attributes

class djangofloor.views.search.SiteSearchView(**kwargs)[source]

Abstract site-wide search view

format_result(obj)[source]

format a single result as a table row

formatted_header()[source]

provide the header row

formatted_results(paginated_results)[source]

return all results in a formatted form as an iterator

get(request, *args, **kwargs)[source]

Get method (use GET data for filling the form)

get_or_post(request, form)[source]

Common result, the same for GET or POST data. Takes a bound form.

get_query(request, pattern)[source]

given the search pattern provided by the user, return the search query

get_template_values(request)[source]

provide extra template values

post(request)[source]

Post method (use POST data for filling the form)

template_name = 'djangofloor/bootstrap3/search.html'

used template for displaying the results

class djangofloor.views.search.UserSearchView(**kwargs)[source]

Search across all users with username and email

format_result(obj)[source]

a bit better formatted row

formatted_header()[source]

headers row

searched_attributes = ['username__icontains', 'email__icontains']

search in usernames and emails

sort_attributes = ['last_name', 'first_name']

order results by last_name and first_name

djangofloor.wsgi.aiohttp_runserver

djangofloor.wsgi.aiohttp_runserver.get_application()[source]
djangofloor.wsgi.aiohttp_runserver.get_http_request(aiohttp_request)[source]

Build a Django request from a aiohttp request: required to get sessions and topics.

Parameters:aiohttp_request (Request) – websocket request provided
djangofloor.wsgi.aiohttp_runserver.handle_redis(window_info, ws, subscriber)[source]

handle the Redis pubsub connection

djangofloor.wsgi.aiohttp_runserver.handle_ws(window_info, ws)[source]

process each event received on the websocket connection.

Parameters:
djangofloor.wsgi.aiohttp_runserver.websocket_handler(request)[source]

djangofloor.wsgi.django_runserver

class djangofloor.wsgi.django_runserver.DjangoWebSocket(wsgi_input)[source]
class djangofloor.wsgi.django_runserver.Stream(wsgi_input)[source]

Wraps the handler’s socket/rfile attributes and makes it in to a file like object that can be read from/written to by the lower level websocket api.

fileno
read
write
class djangofloor.wsgi.django_runserver.WebsocketRunServer(redis_connection=None)[source]
WS_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
WS_VERSIONS = ('13', '8', '7')
flush_websocket(websocket)[source]
get_ws_file_descriptor(websocket)[source]
select(rlist, wlist, xlist, timeout=None)[source]
upgrade_websocket(environ, start_response)[source]

Attempt to upgrade the socket environ[‘wsgi.input’] into a websocket enabled connection.

ws_receive_bytes(websocket)[source]
ws_send_bytes(websocket, message)[source]
djangofloor.wsgi.django_runserver.django_application(environ, start_response)[source]

Return a WSGI application which is patched to be used with websockets.

Returns:a HTTP app, or a WS app (depending on the URL path)
djangofloor.wsgi.django_runserver.run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=<class 'django.core.servers.basehttp.WSGIServer'>)[source]

Function to monkey patch the internal Django command: manage.py runserver

djangofloor.wsgi.exceptions

exception djangofloor.wsgi.exceptions.FrameTooLargeException[source]

Raised if a received frame is too large.

exception djangofloor.wsgi.exceptions.HandshakeError[source]

Raised if an error occurs during protocol handshake.

exception djangofloor.wsgi.exceptions.NoWindowKeyException[source]

raise when the middleware DjangoFloorMiddleware is not used.

exception djangofloor.wsgi.exceptions.UpgradeRequiredError[source]

Raised if protocol must be upgraded.

exception djangofloor.wsgi.exceptions.WebSocketError[source]

Raised when an active websocket encounters a problem.

djangofloor.wsgi.gunicorn_runserver

djangofloor.wsgi.topics

Convert a websocket topic to a string

Each webpage using websockets is connected to several topics. Other websocket topics can be any Python object that will be serialized to a string. Two topics are transparently automatically added: BROADCAST and WINDOW. The default serializer should be sufficient for any Django models, but of course you can override it with the WEBSOCKET_TOPIC_SERIALIZER setting.

djangofloor.wsgi.topics.serialize_topic(window_info, obj)[source]

The default serialization function can serialize any Python object with the following rules

  • djangofloor.tasks.BROADCAST to ‘-broadcast’
  • djangofloor.tasks.WINDOW to ‘-window.’ + the unique window key provided by the djangofloor.wsgi.window_info.WindowInfo object,
  • django.db.models.Model as “app_label.model_name.primary_key”
  • djangofloor.tasks.USER to converted to the authenticated user then serialized as any Django model,
  • django.wsgi.window_info.Session serialized to “-session.key”
  • other objects are serialized to “class.hash(obj)”

djangofloor.wsgi.window_info

WindowInfo object and associated functions

A WindowInfo object carries important data the browser window that is connected to the websocket:

  • the window key that is unique to each open browser window,
  • user information about the connected user,
  • i18n information.

The WindowInfo object is populated by middlewares, thath should be any subclass of djangofloor.middleware.WindowInfoMiddleware. The list of used middlewares is defined by the WINDOW_INFO_MIDDLEWARES setting.

Warning

Never modify a WindowInfo: the same object can be reused for all websocket signals from a given connection.

Designed to be instanciated from a django.http.request.HttpRequest and reused across signals (when a signal calls another one). However, a blank WindowInfo can also be directly instanciated.

class djangofloor.wsgi.window_info.Session(key=None)[source]
class djangofloor.wsgi.window_info.WindowInfo(init=True)[source]

Built to store the username and the window key and must be supplied to any Python signal call. All attributes are set by “WindowInfoMiddleware“‘s.

Can be constructed from a standard django.http.HttpRequest or from a dict. Like the request, you should check the installed middlewares to obtain the full list of attributes. The default ones are provided by djangofloor.middleware.

classmethod from_dict(values)[source]

Generate a new WindowInfo from a dict.

classmethod from_request(request)[source]

return a djangofloor.wsgi.window_info.WindowInfo from a django.http.HttpRequest.

If the request already is a djangofloor.wsgi.window_info.WindowInfo, then it is returned as-is (not copied!).

Parameters:request (django.http.HttpRequest or djangofloor.wsgi.window_info.WindowInfo) – standard Django request
Returns:a valid request
Return type:djangofloor.wsgi.window_info.WindowInfo
has_perm(req, perm)

return true is the user has the required perm.

>>> from djangofloor.wsgi.window_info import WindowInfo
>>> r = WindowInfo.from_dict({'username': 'username', 'perms':['app_label.codename']})
>>> r.has_perm('app_label.codename')
True
Parameters:
  • req – WindowInfo
  • perm – name of the permission (“app_label.codename”)
Returns:

True if the user has the required perm

Return type:

bool

perms

set of all perms of the user (set of “app_label.codename”)

to_dict()[source]

Convert this djangofloor.wsgi.window_info.WindowInfo to a dict which can be provided to JSON.

Returns:a dict ready to be serialized in JSON
Return type:dict
user

return the user object if authenticated, else return None

djangofloor.wsgi.window_info.get_window_context(window_info)[source]

Generate a template context from the window_info, equivalent of a template context from a django.http.request.HttpRequest.

djangofloor.wsgi.window_info.render_to_string(template_name, context=None, window_info=None, using=None)[source]

Render a template to a string using a context from the window_info, equivalent of the django.template.loader.render_to_string().

djangofloor.wsgi.wsgi_server

Structure of the redis database, with prefix = settings.WEBSOCKET_REDIS_PREFIX:

  • pubsub topics “{prefix}{topic}” where topic-key is given by the user-defined function
  • HSET “{prefix}topics-{window-key}” to list of topics with EXPIRE
class djangofloor.wsgi.wsgi_server.WebsocketWSGIServer(redis_connection=None)[source]
assure_protocol_requirements(environ)[source]
default_response()[source]
flush_websocket(websocket)[source]
get_ws_file_descriptor(websocket)[source]
static process_request(request)[source]
static process_subscriptions(request)[source]
process_websocket(window_info, websocket, channels)[source]
static publish_message(window_info, message)[source]
select(rlist, wlist, xlist, timeout=None)[source]
upgrade_websocket(environ, start_response)[source]
ws_receive_bytes(websocket)[source]
ws_send_bytes(websocket, message)[source]
djangofloor.wsgi.wsgi_server.get_websocket_topics(request)[source]

Indices and tables