Compare commits

..
14 Commits
1337 changed files with 28 additions and 270430 deletions
-20
View File
@@ -1,20 +0,0 @@
{
"presets": [
["env", {
"modules": false
}]
],
"plugins": ["transform-runtime"],
"env": {
"production": {
"plugins": [
"lodash"
]
},
"test": {
"plugins": [
"rewire"
]
}
}
}
-23
View File
@@ -1,23 +0,0 @@
[run]
source =
cms
menus
branch = False
omit =
cms/migrations/*
cms/south_migrations/*
cms/tests/*
cms/test_utils/*
menus/migrations/*
menus/south_migrations/*
docs/*
env/*
env*
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if __name__ == .__main__.:
-3
View File
@@ -1,3 +0,0 @@
repo_token: 4byOdcHPvnUGJ5DL2cwLZccI5HUKKxkVJ
service_name: travis-ci
parallel: true
-30
View File
@@ -1,30 +0,0 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.py]
max_line_length = 120
quote_type = single
[*.{scss,js,html}]
max_line_length = 120
indent_style = space
quote_type = double
[*.js]
max_line_length = 120
quote_type = single
[*.rst]
max_line_length = 80
[*.yml]
indent_size = 2
-233
View File
@@ -1,233 +0,0 @@
module.exports = {
env: {
browser: true,
node: true,
jquery: true,
jasmine: true,
es6: true
},
globals: {
CMS: true,
Promise: true,
__CMS_VERSION__: true
},
root: true,
ecmaFeatures: {
modules: true
},
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
plugins: ['compat'],
settings: {
polyfills: ['document-currentscript']
},
rules: {
// Possible Errors
'comma-dangle': [2, 'never'],
'no-cond-assign': 2,
'no-console': 1,
'no-constant-condition': 2,
'no-control-regex': 2,
'no-debugger': 2,
'no-dupe-args': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty': ['error', { allowEmptyCatch: true }],
'no-ex-assign': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': 0,
'no-extra-semi': 2,
'no-func-assign': 2,
'no-inner-declarations': 2,
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-negated-in-lhs': 2,
'no-obj-calls': 2,
'no-regex-spaces': 2,
'no-sparse-arrays': 2,
'no-unexpected-multiline': 2,
'no-unreachable': 2,
'use-isnan': 2,
'valid-jsdoc': [
2,
{
requireReturn: false,
requireParamDescription: false,
requireReturnDescription: false,
prefer: {
return: 'returns'
}
}
],
'valid-typeof': 2,
// Best Practices
'accessor-pairs': 2,
'block-scoped-var': 2,
complexity: ['error', { max: 10 }],
'consistent-return': 0,
curly: 2,
'default-case': 2,
'dot-location': [2, 'property'],
'dot-notation': 2,
eqeqeq: 2,
'guard-for-in': 2,
'no-alert': 2,
'no-caller': 2,
'no-case-declarations': 2,
'no-div-regex': 2,
'no-else-return': 1,
'no-empty-pattern': 2,
'no-eq-null': 2,
'no-eval': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-implicit-coercion': 0,
'no-implied-eval': 2,
'no-invalid-this': 0,
'no-iterator': 2,
'no-labels': 2,
'no-lone-blocks': 2,
'no-loop-func': 2,
'no-magic-numbers': [
'error',
{
ignore: [0, -1, 1, 2],
ignoreArrayIndexes: true
}
],
'no-multi-spaces': 2,
'no-multi-str': 0,
'no-native-reassign': 2,
'no-new-func': 2,
'no-new-wrappers': 2,
'no-new': 0,
'no-octal-escape': 2,
'no-octal': 2,
'no-param-reassign': 2,
'no-process-env': 0,
'no-proto': 2,
'no-redeclare': 2,
'no-return-assign': 2,
'no-script-url': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-throw-literal': 2,
'no-unused-expressions': [2, { allowShortCircuit: true }],
'no-useless-call': 2,
'no-useless-concat': 2,
'no-void': 2,
'no-warning-comments': 0,
'no-with': 2,
radix: 2,
'vars-on-top': 0, // FIXME should be enabled at some point
'wrap-iife': [2, 'inside'],
yoda: [2, 'never', { exceptRange: true }],
// Strict Mode
strict: 0, // not required with webpack
// Variables
'init-declarations': 0,
'no-catch-shadow': 2,
'no-delete-var': 2,
'no-label-var': 2,
'no-shadow-restricted-names': 2,
'no-shadow': 2,
'no-undef-init': 2,
'no-undef': 2,
'no-undefined': 0,
'no-unused-vars': 2,
'no-use-before-define': 2,
// Stylistic Issues
'array-bracket-spacing': [2, 'never'],
'block-spacing': 2,
'brace-style': [2, '1tbs'],
camelcase: 0,
'comma-spacing': [2, { before: false, after: true }],
'comma-style': [2, 'last'],
'computed-property-spacing': [2, 'never'],
'consistent-this': [2, 'that'],
'eol-last': 2,
'func-names': 0,
'func-style': 0,
'id-length': 0,
'id-match': 0,
indent: [
'error',
4,
{
SwitchCase: 1
}
],
'jsx-quotes': 0,
'key-spacing': [2, { beforeColon: false, afterColon: true }],
'linebreak-style': [2, 'unix'],
'lines-around-comment': 0,
'max-nested-callbacks': [2, 5],
'new-cap': 2,
'new-parens': 2,
'newline-after-var': 2,
'no-array-constructor': 2,
'no-continue': 2,
'no-inline-comments': 0,
'no-lonely-if': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multiple-empty-lines': [2, { max: 2 }],
'no-negated-condition': 2,
'no-nested-ternary': 2,
'no-new-object': 2,
'no-restricted-syntax': 0,
'no-spaced-func': 0,
'no-ternary': 0,
'no-trailing-spaces': 2,
'no-underscore-dangle': 0,
'no-unneeded-ternary': 2,
'object-curly-spacing': [
2,
'always',
{
objectsInObjects: true,
arraysInObjects: true
}
],
'one-var': [2, 'never'],
'operator-assignment': 2,
'operator-linebreak': 0,
'padded-blocks': 0,
'quote-props': [2, 'as-needed'],
quotes: [2, 'single', 'avoid-escape'],
'require-jsdoc': 2,
'semi-spacing': [2, { before: false, after: true }],
semi: [2, 'always'],
'sort-vars': 0,
'keyword-spacing': 2,
'space-before-blocks': 2,
// FIXME reenable after running prettier on full codebase
'space-before-function-paren': [0, { anonymous: 'never', named: 'never' }],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': 2,
'spaced-comment': 2,
'wrap-regex': 0,
// ES6
'arrow-parens': [2, 'as-needed'],
// Legacy
'max-depth': [2, 4],
'max-len': [2, 120],
'max-params': [2, 3],
'max-statements': 0,
'no-bitwise': 2,
'no-plusplus': 0,
'compat/compat': 2
}
};
-78
View File
@@ -1,78 +0,0 @@
##########################
Contributing to django CMS
##########################
Like every open-source project, django CMS always welcomes contributions.
*******************************
GitHub issues and pull requests
*******************************
`GitHub issues <https://github.com/divio/django-cms/issues>`_ should only be used to report *suspected issues* in
django CMS.
It should **not** be used for reporting *security issues* - please use `security@django-cms.org
<security@django-cms.org>`_ for those.
For *help with django CMS*, please use our `users' email list <https://groups.google.com/forum/#!forum/django-cms>`_.
For *feature requests*, please use our `developers' email list
<https://groups.google.com/forum/#!forum/django-cms-developers>`_.
**************************
Contribution documentation
**************************
We maintain comprehensive `contribution documentation <http://docs.django-cms.org/en/latest/contributing/>`_ - please
familiarise yourself with it.
***************
Security issues
***************
Security issues should not be reported **anywhere** except via our dedicated and private `security@django-cms.org
<security@django-cms.org>`_ email address.
If you think you have discovered a security issue in our code, please do not raise it on
* IRC
* GitHub
* Twitter
* either of our email lists
or in any other public forum until we have had a chance to deal with it.
***************
Code of conduct
***************
django CMS is governed by a `Code of Conduct
<http://docs.django-cms.org/en/latest/contributing/code_of_conduct.html>`_. All participants in our community and its
various forums are expected to abide by it.
*********
Community
*********
You can join us online:
* in our IRC channel, #django-cms, on ``irc.freenode.net``. If you don't have an IRC client, you can
`join our IRC channel using the KiwiIRC web client
<https://kiwiirc.com/client/irc.freenode.net/django-cms>`_, which works pretty well.
* on our `django CMS users email list <https://groups.google.com/forum/#!forum/django-cms>`_ for
**general** django CMS questions and discussion
* on our `django CMS developers email list
<https://groups.google.com/forum/#!forum/django-cms-developers>`_ for discussions about the
**development of django CMS**
You can also follow:
* the `Travis Continuous Integration build reports <https://travis-ci.org/divio/django-cms>`_
* the `@djangocms`_ Twitter account for general announcements
.. _@djangocms : https://twitter.com/djangocms
-30
View File
@@ -1,30 +0,0 @@
<!-- Important!
Only use this form if you suspect that you have discovered an issue in django CMS.
Do not use this form for reporting security issues - please use security@django-cms.org for those.
For help with django CMS, please use our users' email list: https://groups.google.com/forum/#!forum/django-cms.
For feature requests, please use our developers' email list: https://groups.google.com/forum/#!forum/django-cms-developers.
-->
### Summary
<!-- If this is a security issue stop right here and email security@django-cms.org instead -->
### Expected behaviour
### Actual behaviour
### Environment
* Python version:
* Django version:
* django CMS version:
-33
View File
@@ -1,33 +0,0 @@
<!--
If this is a security-related patch stop immediately!
See http://docs.django-cms.org/en/latest/contributing/development-policies.html
-->
### Summary
Fixes #
### Links to related discussion
### Proposed changes in this pull request
### Documentation checklist
* [ ] I have updated CHANGELOG.txt if appropriate
* [ ] I have updated the release notes document if appropriate, with:
* [ ] general notes
* [ ] bug-fixes
* [ ] improvements/new features
* [ ] backwards-incompatible changes
* [ ] required upgrade steps
* [ ] names of contributors
* [ ] I have updated other documentation
* [ ] I have added my name to the AUTHORS file
* [ ] This PR's documentation has been approved by Daniele Procida
-51
View File
@@ -1,51 +0,0 @@
*.pyc
!.travis.yml
*.swp
*.lock
*.log
*.pid
cms/django
*.DS_Store
*.svn
*.xml
/*env*/
/*docs/env*/
/*docs/build*/
*.sqlite
cms/media/cms_page_media/
example/run
example/local_settings.py
reversion/
htmlcov
build
dist
.ropeproject
.project
.coverage
.pydevproject
.vscode
.settings
/*.egg-info/
/*.egg/
develop-eggs
downloads
eggs
parts
bin
/dist
*~
distribute-*.tar.gz
include/
lib/
man/
share/
!.editorconfig
!.jscsrc
!.jshintrc
cms/static/cms/compass_app_log.txt
!cms/static/cms/js/dist
cms/tests/frontend/coverage
node_modules
screenshots
.idea
.better_test.db
-15
View File
@@ -1,15 +0,0 @@
doc-warnings: yes
test-warnings: no
strictness: veryhigh
max-line-length: 120
uses:
- django
autodetect: yes
ignore-paths:
- docs
- cms/migrations
- cms/migrations_django
- cms/tests/*
- cms/test_utils/*
- menus/migrations/*
- menus/migrations_django/*
-148
View File
@@ -1,148 +0,0 @@
language: python
python:
- 3.6
- 3.5
- 3.4
- 2.7
sudo: false
addons:
apt:
packages:
- enchant
cache:
directories:
- node_modules
- $HOME/.pip/cache
env:
global:
# coveralls
- secure: GzxbqfktWQkf6QQvz0OMk4zHbGI8QWcThTLUzEtktheInxwivOHnoJ1kQu2jVVrk0ZVPbOedE+Cn3QMfi6Wj+y6CREwIhfyzqZV+BIgYu/MpW7vT1BQGiN2suHjFOt/TJ90G41DlBRDc7FxGLqL1Mq8hsEdE0W+/Yszo3aMbp2w=
# sauce labs username
- secure: RotktnZ0AqeTDYfh2O472pPiolQJ2ZDRPUKGDajYWgnG2n94K7hBd4pnA1H/cd42sOyReC3lmmweWNSbG0DLD9+X+s0fwaqKcWGnYsLAcjOWaNXPMwvvgaivadT34JmS9Wv29zNudPL2A6zNw0CB+YVxUZIA4Cm9984AxbYJGSk=
# sauce labs access token
- secure: CbPfysSncBB2Ue+VOtLDa8xJvwKl73nJO647zt/9UvZ/3PilnZN9aZv2jHxGvCiFXcez+2AddKptMCcx/5EW5UfRkrWUDHrfLCULU2TfOjmufEGM1eOIXhiAun8WQ85LBzTAYy1r9D514cbU3Yzn3xGZwJljPE8JE4cx3MNN/qQ=
# temporary solution until https://github.com/ariya/phantomjs/issues/13953 is resolved
- PHANTOMJS_CDNURL=https://s3.amazonaws.com/aldryn-local-assets
matrix:
include:
# FRONTEND
- python: 3.6
env: FRONTEND=1 UNIT=1
- python: 3.6
env: FRONTEND=1 LINT=1
- python: 3.6
env: FRONTEND=1 INTEGRATION=1 INTEGRATION_TESTS_BUCKET=1 DJANGO=1.11 DATABASE_URL='sqlite://localhost/testdb.sqlite'
- python: 3.6
env: FRONTEND=1 INTEGRATION=1 INTEGRATION_TESTS_BUCKET=2 DJANGO=1.11 DATABASE_URL='sqlite://localhost/testdb.sqlite'
- python: 3.6
env: FRONTEND=1 INTEGRATION=1 INTEGRATION_TESTS_BUCKET=3 DJANGO=1.11 DATABASE_URL='sqlite://localhost/testdb.sqlite'
# DJANGO 1.11
- python: 2.7
env: DJANGO=1.11 DATABASE_URL='sqlite://localhost/:memory:'
- python: 3.4
env: DJANGO=1.11 DATABASE_URL='sqlite://localhost/:memory:'
- python: 3.5
env: DJANGO=1.11 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=1.11 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=1.11 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
- python: 3.6
env: DJANGO=1.11 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
# DJANGO 2.0
- python: 3.4
env: DJANGO=2.0 DATABASE_URL='sqlite://localhost/:memory:'
- python: 3.5
env: DJANGO=2.0 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=2.0 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=2.0 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
- python: 3.6
env: DJANGO=2.0 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
# DJANGO 2.1
- python: 3.5
env: DJANGO=2.1 DATABASE_URL='sqlite://localhost/:memory:'
- python: 3.5
env: DJANGO=2.1 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=2.1 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=2.1 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
- python: 3.6
env: DJANGO=2.1 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
- python: 3.6
env: TEST_DOCS=1 DJANGO=2.1 DATABASE_URL='sqlite://localhost/:memory:'
# DJANGO 2.2
- python: 3.5
env: DJANGO=2.2 DATABASE_URL='sqlite://localhost/:memory:'
dist: xenial
sudo: true
- python: 3.5
env: DJANGO=2.2 DATABASE_URL='mysql://root@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=2.2 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test'
- python: 3.6
env: DJANGO=2.2 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='emailuserapp.EmailUser'
- python: 3.6
env: DJANGO=2.2 DATABASE_URL='postgres://postgres@127.0.0.1/djangocms_test' AUTH_USER_MODEL='customuserapp.User'
- python: 3.6
env: TEST_DOCS=1 DJANGO=2.2 DATABASE_URL='sqlite://localhost/:memory:'
dist: xenial
sudo: true
allow_failures:
- python: 2.7
env: DJANGO=1.11 DATABASE_URL='sqlite://localhost/:memory:'
exclude:
- python: 2.7
- python: 3.4
- python: 3.5
- python: 3.6
fast_finish: true
before_script:
- pip freeze
- if [ "$DATABASE_URL" == "postgres://postgres@127.0.0.1/djangocms_test" ]; then psql -c "DROP DATABASE IF EXISTS djangocms_test;" -U postgres; fi
- if [ "$DATABASE_URL" == "postgres://postgres@127.0.0.1/djangocms_test" ]; then psql -c "create database djangocms_test;" -U postgres; fi
- if [ "$DATABASE_URL" == "mysql://root@127.0.0.1/djangocms_test" ]; then mysql -e 'create database IF NOT EXISTS djangocms_test CHARACTER SET utf8 COLLATE utf8_general_ci;'; fi
before_install:
- "export TRAVIS_COMMIT_MSG=\"$(git log --format=%B --no-merges -n 1)\""
- pip install -U pip>=8.0
- echo "$TRAVIS_COMMIT_MSG" | grep '\[skip saucelabs\]'; export USE_SAUCE_LABS=$?; true
- echo "$TRAVIS_COMMIT_MSG" | grep '\[ci only docs\]'; export ONLY_DOCS=$?; true
install:
- if [ "$UNIT" != 1 ] && [ "$LINT" != 1 ]; then pip install -r "test_requirements/django-$DJANGO.txt"; pip freeze; fi
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then nvm install 6 && nvm use 6; fi
# Disable the spinner, it looks bad on Travis
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then npm config set spin false; fi
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then npm install -g gulp@3.9.1; fi
- if [ "$FRONTEND" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then scripts/install-npm-dependencies.sh; fi
- if [ "$UNIT" != 1 ] && [ "$LINT" != 1 ] && [ "$DATABASE_URL" == 'postgres://postgres@127.0.0.1/djangocms_test' ]; then pip install psycopg2 ; fi
- if [ "$UNIT" != 1 ] && [ "$LINT" != 1 ] && [ "$DATABASE_URL" == 'mysql://root@127.0.0.1/djangocms_test' ]; then pip install mysqlclient ; fi
script:
- if [ "$FRONTEND" == 1 ] && [ "$UNIT" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then gulp tests:unit; fi;
- if [ "$FRONTEND" == 1 ] && [ "$LINT" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then gulp lint; fi;
- if [ "$FRONTEND" == 1 ] && [ "$INTEGRATION" == 1 ] && [ "$ONLY_DOCS" != 0 ]; then pip install -e .; pip freeze; gulp tests:integration; fi;
- if [ "$FRONTEND" != 1 ] && ([ "$TEST_DOCS" == 1 ] && [ "$ONLY_DOCS" == 0 ] || [ "$ONLY_DOCS" != 0 ]); then coverage run --parallel-mode manage.py test; fi;
- if [ "$FRONTEND" != 1 ] && ([ "$TEST_DOCS" == 1 ] && [ "$ONLY_DOCS" == 0 ] || [ "$ONLY_DOCS" != 0 ]); then coverage combine; fi;
after_success: if [ "$FRONTEND" != 1 ]; then coveralls; fi;
notifications:
irc:
- irc.freenode.org#django-cms
- irc.freenode.org#django-cms-sprint
-13
View File
@@ -1,13 +0,0 @@
[django-cms.js]
file_filter = cms/locale/<lang>/LC_MESSAGES/djangojs.po
source_file = cms/locale/en/LC_MESSAGES/djangojs.po
source_lang = en
[main]
host = https://www.transifex.com
[django-cms.core]
file_filter = cms/locale/<lang>/LC_MESSAGES/django.po
source_file = cms/locale/en/LC_MESSAGES/django.po
source_lang = en
-562
View File
@@ -1,562 +0,0 @@
Current or previous core committers:
* Angelo Dini
* Beni Wohlwend
* Chris Glass
* Daniele Procida
* Eric Robitaille
* Iacopo Spalletti
* Jonas Obrist
* Martin Koistinen
* Patrick Lauber
* Paulo Alvarado
* Peter Ciciman
* Stefan Foulis
* Øyvind Saltvik
Current and previous core designers:
* Christian Bertschy
* Matthias Nüesch
Contributors (based on gitlog, 537 unique authors):
* Антон Евжаков
* A. Bram Neijt
* aaloy
* Aaron Fay
* Aaron Renner
* Aaron Spike
* aball
* Adam Chainz
* Adi Sieker
* Admin Adminaras
* Adrien Brunet
* Adrián Ribao Martínez
* Aidas Bendoraitis
* Alberto Paro
* Ales Kocjancic
* Ales Zabala Alava (Shagi)
* Alessandro Pasotti
* Alessandro Ronchi
* Alex Cucu
* Alex Marandon
* Alexander Paramonov
* Alexandre Leray
* Alexey Subbotin
* Aliaksei Urbanski
* alskgj
* Alvin Mites
* Anatoly Ivanov
* Andi Albrecht
* Andras Gyömrey
* Andre Bossard
* Andrea Stagi
* Andreas Elvers
* Andrei Avram
* Andrei Fokau
* Andrew Schoen
* André Cruz
* Angelo Dini
* angular_circle
* Anthony Steinhauser
* Antoine Catton
* Anton Parkhomenko
* AQNOUCH Mohammed
* Arcady Usov
* Arne Gellhaus
* Arne Schauf
* Artem Skoretskiy
* Arthur Debert
* Artis Avotins
* Aymeric Augustin
* Bartosz Płóciennik
* Basile LEGAL
* behrooz
* Behrooz Nobakht
* Belegnar
* Ben Northway
* Ben Stähli
* Benjamin
* Benjamin Liles
* Benjamin Wohlwend
* benjaoming
* benzkji
* Bernardo Cabezas Serra
* Bernd Zeimetz
* Bertrand Bordage
* beshrkayali
* Bitdeli Chef
* Bjorn Sandberg
* Bob Karreman
* Bouke Haarsma
* Brad Walker
* Braden MacDonald
* Brian Schott
* brightwhitefox
* Calvin Cheng
* Carlo Ascani
* Carlos de Paula
* casio
* centralniak
* Cezar Pendarovski
* cezar77
* Chanita Siridechkun
* Charlie Denton
* Charpentier Johan
* Chematronix
* Cheng-Chia Tseng
* Chris Adams
* Chris Franklin
* Chris Glass
* Chris Hughes
* Chris Jones
* Chris Wesseling
* Chris Wilson
* Christian Strappazzon
* Christof Hagedorn
* Christopher Grebs
* Christos Kopanos
* cip
* Claudio Bartolini
* Colin Wirz
* Conrado Buhrer
* Corey Farwell
* Craig de Stigter
* Craig Ricciuto
* creakings
* cworth-gh
* Damian Moore
* Damir Arbula
* DamirArbula
* Daniel Barden
* daniele
* Daniele Procida
* Danilo Bargen
* DaNmarner
* darbula
* Darii Denis
* Darii Denis Home
* Dario Albanesi
* Darren Pearce
* Darryl Woods
* Dave Bell
* Dave Hall
* David D Lowe
* David Grant
* David Jean Louis
* David Lam
* David Lee
* David Thompson
* Davide Setti
* derek73
* deshack
* digi604
* divio
* Diógenes Augusto Fernandes Hermínio
* dlamotte
* Dmitry Akinin
* Dmitry Gorelik
* Dmytro Gorelik
* Donatas
* Donatas Kučinskas
* Douwe van der Meij
* Dries Desmet
* dstufft
* Dylann CORDEL
* DylannCordel
* DZ
* eged
* Egor V. Nazarkin
* Ekrem SEREN
* Elias Probst
* Eraldo Energy
* Eric Amador
* Eric Eldredge
* Eric Robitaille
* ericr
* Erik Allik
* Erlend Dalen
* Eugene MechanisM
* Evandro Miquelito
* evildmp
* F. Gabriel Gosselin
* f4nt
* fcurella
* febsn
* Filip Kazimierczak
* Filipe Waitman
* FinalAngel
* fivethreeo
* fj
* floppya
* Florian Verdet
* Frank
* Frank Bieniek
* FrankBie
* frost-nzcr4
* furiousdave
* Gabe Jackson
* Gabriel Hurley
* GaretJax
* Gavin Wahl
* genti94
* Gentian Mazrekaj
* gentleShark
* Geoffrey Fairchild
* Geoffrey Hing
* George Marshall
* Gerard Świderski
* Germano Gabbianelli
* Gianluca Guarini
* Gilles Lenfant
* Gleb Chipiga
* Gokmen Gorgen
* Gregory Klupar
* guandalino
* Hamish Downer
* Hans Andersen
* hedberg
* henning
* Henning Sprang
* heppstux
* Herbert Poul
* hjkelly
* Horst Gutmann
* hysia
* Iacopo Spalletti
* Ian Lewis
* ikudryavtsev
* indexofire
* Ionel Maries Cristian
* Ivan Vershigora
* Iván Raskovsky
* izi
* J. Cliff Dyer
* Jacob Rief
* Jakob Hedman
* Jakub Randák
* jalaziz
* Jameel Al-Aziz
* James Richards
* James Rutherford
* Jana Deutschlaender
* Jannik V
* Jannis Leidel
* Janusz Harkot
* Jared Proffitt
* Jason Brown
* Jason Davies
* Jason Jenkins
* Jason Robinson
* Jeffrey Goettsch
* Jens Diemer
* JensDiemer
* Jeroen
* Jeroen Noten
* Jessica Tallon
* Jimmy Lam
* Johannes Bornhold
* John Bazik
* John-Scott Atlakson
* Jon Prindiville
* Jonas Obrist
* Jonathan Liuti
* Jonathan Stoppani
* jordanjambazov
* Jorge Vargas
* Jos van Velzen
* Joseph Bergantine
* Joseph Lin
* Joseph Melettukunnel
* Josh Kalderimis
* Josh Schneier
* João Luiz Lorencetti
* Julien Poissonnier
* Julz
* Júlio R. Lucchese
* Kalle Bronsen
* kater169
* Katie McLaughlin
* kblomqvist
* Kegan Holtzhausen
* Keryn Knight
* Kevin Burton
* Kevin Funk
* Kevin Richardson
* Kim Thoenen
* kochin
* Kristian Øllegaard
* Krzysztof Socha
* kunitoki
* L-A Iscla
* Lars Smit
* Lars van de Kerkhof
* Laurens Bosscher
* Leon Smith
* Leonardo
* Lio Mendonca
* Lionardo Mendonça
* littlepea
* Loriana Indelicato
* lovmat
* Lucas Vogelsang
* Lucio Asnaghi
* Ludwig Hähne
* lug
* Luis Diego Garcia
* Luke Crooks
* Luke Plant
* m000
* Maik Lustenberger
* maiklust
* Manolis Stamatogiannakis
* Manuel Schmidt
* Marc-Olivier Titeux
* Marco Badan
* Marco Bonetti
* Marco Federighi
* Marco Paolini
* Marco Rimoldi
* marcor
* Marijn Goedegebure
* Mark
* Mark Rogers
* Mark Walker
* Markus Holtermann
* Markus Zapke-Gründemann
* Marti Raudsepp
* Martin
* Martin Bambas
* Martin Brochhaus
* Martin Koistinen
* Martin Kosír
* Martin Owens
* Martin Pajuste
* martinkosir
* Matas Dailyda
* Mateusz Dereniowski
* Mateusz Kamycki
* Mateusz Marzantowicz
* mathijs
* Matt Chisholm
* Matteo Rosati
* Matthias Cavigelli
* Mattia Larentis
* Max Shkurygin
* Maxim Bodyansky
* Maxime Haineault
* mbouchar
* mcosta
* meers
* Mel Collins
* meomap
* MerLex
* Michael P. Jung
* Michael Thornhill
* Michal Danilak
* Mike Dory
* Mike Johnson
* mikek
* Mikko Ahlroth
* Milo Price
* Mitar
* Mokys
* Morgan Wahl
* Motiejus Jakštys
* motleytech
* mrlundis
* mvaerle
* MW
* Mykhailo Kolesnyk
* Nelson Brochado
* neoprolog
* Nick Jones
* Nicolas
* Nicolas PASCAL
* nikolas
* Nina Zakharenko
* nsh
* o-zander
* ojii
* Oliver Thane
* Oliver Zander
* Olivier Larchevêque
* Olivier Le Brouster
* Orne Brocaar
* Oyvind Saltvik
* padelt
* Pankrat
* Paolo Leggio
* Paolo Romolini
* Pascal Mouret
* pascal.beyeler
* Patrick Arminio
* Patrick Lauber
* Patrick Toal
* patricklauber
* Patryk Zawadzki
* paul
* Paul van der Linden
* Paulo
* Paulo Alvarado
* Pavel Puchkin
* Pawel Markowski
* pbgc
* pcicman
* Pedro Gracia
* Pete Loggie
* Peter Farrell
* Peter J. Farrell
* Peter Landry
* Peter-Paul van Gemerden
* peterfarrell
* Petteri
* pgcd
* Philipp Bosch
* Philipp Zedler
* philomat
* phuihock
* Pigletto
* Piotr Jakimiak
* Piotr Kilczuk
* piquadrat
* pumalo
* Rafal Radulski
* raidsan
* Rainer Koirikivi
* Rajesh
* Rajesh K.
* Rajesh P.
* Rebecca Breu
* Remco Wendt
* René Fleschenberg
* requires.io
* Restless Being
* Richard Barran
* rizumu
* Robert Barsch
* Robert Buchholz
* Robert Clark
* Robert Feldbinder
* Robert Pogorzelski
* Robert Stein
* Roberto
* Roberto Bampi
* Robin Lewis
* robint
* Rodolfo Carvalho
* Roman Kozlovskiy
* root
* rtpm
* Ruslan Malogulko
* Russ Ferriday
* SachaMPS
* Salmanul Farzy
* Sam Manzi
* Samuel Lahti
* Samuel Luescher
* Samuel Lüscher
* sbussetti
* Schoen
* scottbarnham
* sealibora
* Sean Bleier
* Sebastian Braun
* Sergey Fedoseev
* Seth Buntin
* Seyhun Akyurek
* Shahar Or
* Shatalov Vadim
* Shaun Brady
* shed
* Shinya Okano
* shulcsm
* Simon Charette
* Simon Hedberg
* Simon Meers
* sleytr
* Sophie Leroy
* spookylukey
* srj55
* ssteinerX
* Stavros Korokithakis
* Stefan Foulis
* Stefan Wehrmeyer
* stefanfoulis
* Stefano Brentegani
* Stefano Crosta
* Stefano Morandi
* Stefano Probst
* Steffen Jasper
* Stephan
* Stephan Hepper
* Stephan Herzog
* Stephan Jaekel
* Stephen Muss
* Stephen Paulger
* Stephen Watkin
* Steve Steiner
* Steven Laroche
* Sylvain Fankhauser
* Tadas Dailyda
* Tanel Külaots
* tdelam
* tehfink
* Thibaud Colas
* Thomas Meitz
* Thomas Parslow
* Thomas Remmert
* Thomas Woolford
* thomas-d
* Tim Anderegg
* Tim Davies
* Tim Graham
* timesong
* Tino de Bruijn
* tiret
* Tobias von Klipstein
* Tom
* Tom Berger
* Tom de Simone
* Tom S
* Tom V
* Tom Wardill
* Ulrich Petri
* unknown
* uno
* User
* Vadim Lopatyuk
* Vadim Sikora
* Venelin Stoykov
* Viktor Nagy
* Viliam Segeda
* Vinit (Vermicelli on IRC)
* vinit kumar
* Vinod Kurup
* Vladimir Bolshakov
* Vladyslav
* vvangelovski
* wangJunjie
* Wayne Moore
* wid
* wildermesser
* WYSIATI
* Xavier Fernandez
* Xavier Ordoquy
* xie wei
* xray7224
* Yann Malet
* yann.malet@gmail.com
* ychouinard
* yedpodtrzitko
* yohanboniface
* Yosuke Ikeda
* Yuri van der Meer
* zandeez
* zundoya
* Žan Anderle
* Óscar M. Lage
* Øyvind Saltvik
-1107
View File
File diff suppressed because it is too large Load Diff
-28
View File
@@ -1,28 +0,0 @@
Copyright (c) 2008-present, Batiste Bieler
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of the author nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-13
View File
@@ -1,13 +0,0 @@
include AUTHORS
include LICENSE
include README.rst
include CHANGELOG.txt
recursive-include cms/locale *
recursive-include cms/templates *
recursive-include cms/static *
recursive-include cms/plugins *
recursive-include menus/templates *
recursive-include stacks/templates *
recursive-include docs *
recursive-exclude * *.pyc
recursive-exclude * *.scssc
-128
View File
@@ -1,128 +0,0 @@
##########
django CMS
##########
.. image:: https://travis-ci.org/divio/django-cms.svg?branch=develop
:target: https://travis-ci.org/divio/django-cms
.. image:: https://img.shields.io/pypi/v/django-cms.svg
:target: https://pypi.python.org/pypi/django-cms/
.. image:: https://img.shields.io/badge/wheel-yes-green.svg
:target: https://pypi.python.org/pypi/django-cms/
.. image:: https://img.shields.io/pypi/l/django-cms.svg
:target: https://pypi.python.org/pypi/django-cms/
.. image:: https://codeclimate.com/github/divio/django-cms/badges/gpa.svg
:target: https://codeclimate.com/github/divio/django-cms
:alt: Code Climate
Open source enterprise content management system based on the Django framework.
.. image:: https://raw.githubusercontent.com/divio/django-cms/develop/docs/images/try-with-divio.png
:target: http://try.django-cms.org/
:alt: Try demo with Divio Cloud
.. ATTENTION::
To propose **significant new features**, open pull requests based on and made against the **develop** branch. It's
the branch for features that will go into the next django CMS feature release.
To propose **fixes and backwards-compatible improvements**, please work on the latest **release** branch. This is
the branch that will become the next PyPI release ("the next version of django CMS").
Security fixes will be backported to older branches by the core team as appropriate.
********
Features
********
* hierarchical pages
* extensive built-in support for multilingual websites
* multi-site support
* draft/publish workflows
* version control
* a sophisticated publishing architecture, that's also usable in your own applications
* frontend content editing
* a hierarchical content structure for nested plugins
* an extensible navigation system that your own applications can hook into
* SEO-friendly URLs
* designed to integrate thoroughly into other applications
Developing applications that integrate with and take advantage of django CMS features is easy and well-documented.
More information on `our website <https://www.django-cms.org>`_.
************
Requirements
************
See the `Python/Django requirements for the current release version
<http://docs.django-cms.org/en/latest/#software-version-requirements-and-release-notes>`_ in our documentation.
See the `installation how-to guide for an overview of some other requirements and dependencies of the current release
<http://docs.django-cms.org/en/latest/how_to/install.html>`_
*************
Documentation
*************
We maintain documentation for several versions of the project. Key versions are:
* `stable <http://docs.django-cms.org>`_ (default), for the **current release** version
* `latest <http://docs.django-cms.org/en/latest/>`_, representing the latest build of the **release-3.4.x branch**
* `develop <http://docs.django-cms.org/en/develop/>`_, representing the latest build of the **develop branch**
For more information about our branch policy, see `Branches
<http://docs.django-cms.org/en/latest/contributing/development-policies.html>`_.
Our documentation is hosted courtesy of `Read the Docs <https://readthedocs.org>`_.
********
Tutorial
********
http://docs.django-cms.org/en/latest/introduction/index.html
***********
Quick Start
***********
You can use the `django CMS installer <https://djangocms-installer.readthedocs.io>`_::
$ pip install --upgrade virtualenv
$ virtualenv env
$ source env/bin/activate
(env) $ pip install djangocms-installer
(env) $ mkdir myproject && cd myproject
(env) $ djangocms -f -p . my_demo
(env) $ python manage.py
************
Getting Help
************
Please head over to our IRC channel, #django-cms, on irc.freenode.net or write
to our `mailing list <https://groups.google.com/forum/#!forum/django-cms>`_.
If you don't have an IRC client, you can `join our IRC channel using the KiwiIRC web client
<https://kiwiirc.com/client/irc.freenode.net/django-cms>`_, which works pretty well.
******************
Commercial support
******************
This project is backed by `Divio <https://www.divio.com/en/commercial-support/>`_.
If you need help implementing or hosting django CMS, please contact us:
sales@divio.com.
*******
Credits
*******
* Includes icons from `FamFamFam <http://www.famfamfam.com>`_.
* Python tree engine powered by
`django-treebeard <https://tabo.pe/projects/django-treebeard/>`_.
* JavaScript tree in admin uses `jsTree <https://www.jstree.com>`_.
* Many thanks to the
`over 515 contributors <https://github.com/divio/django-cms/blob/develop/AUTHORS>`_
to the django CMS!
-32
View File
@@ -1,32 +0,0 @@
- Put beer in fridge for cooling (see last step)
- Create a release branch (locally)
- Bump the version in cms/__init__.py
- Build newest english translation (django-admin.py makemessages -l en)
- Build newest english JS translations (django-admin.py makemessages -l en -d djangojs)
- Pull translations (tx pull -f -a)
- Compile translations (django-admin.py compilemessages)
- Make sure icons are recreated in the new place (gulp icons)
- Make sure CSS files are compiled from latest sass source (gulp sass)
- Make sure JS files are bundled and minified propery from latest source (gulp bundle)
- Make sure to remove JS, CSS and font files from the older version of CMS
- Check CHANGELOG that all closed PRs are present.
- Prepare blog post, including "Release" tag
- update AUTHORS file (python develop.py authors)
- Make a release commit
- Tag release commit
- Merge back into develop
- Push develop to GitHub
- Merge into master
- Push master to GitHub (WITH --tags)
- Release to PyPI (ONLY FOR FINAL VERSIONS!) (python setup.py sdist upload)
- Release wheel (python setup.py bdist_wheel upload)
- Bump version in develop branch to .dev1
- Publish blog post
- Tweet blog post
- Post notice on django-users, django-cms and django-cms-developers mailing lists
- Update IRC channel topic (/topic this is the new topic)
- Add current version to RTD versions.
- Set new version as default version on RTD
- Make sure tutorials on the website still work
- Make sure download button on homepage (website) is updated
- Have a beer! (If anyone else is around, do a release party!)
-2
View File
@@ -1,2 +0,0 @@
last 2 versions
> 1%
-5
View File
@@ -1,5 +0,0 @@
# -*- coding: utf-8 -*-
__version__ = '3.6.0'
default_app_config = 'cms.apps.CMSConfig'
-11
View File
@@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
import cms.admin.pageadmin
import cms.admin.useradmin
import cms.admin.permissionadmin
import cms.admin.settingsadmin
import cms.admin.static_placeholder # nopyflakes
# Piggyback off admin.autodiscover() to discover cms plugins
from cms import plugin_pool
plugin_pool.plugin_pool.discover_plugins()
-1356
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-182
View File
@@ -1,182 +0,0 @@
# -*- coding: utf-8 -*-
from copy import deepcopy
from django.contrib import admin
from django.contrib.admin import site
from django.contrib.auth import get_user_model
from django.contrib.auth.admin import UserAdmin
from django.contrib.sites.models import Site
from django.db import OperationalError
from django.utils.translation import gettext_lazy as _
from cms.admin.forms import GlobalPagePermissionAdminForm, PagePermissionInlineAdminForm, ViewRestrictionInlineAdminForm
from cms.exceptions import NoPermissionsException
from cms.models import PagePermission, GlobalPagePermission
from cms.utils import permissions, page_permissions
from cms.utils.conf import get_cms_setting
from cms.utils.helpers import classproperty
PERMISSION_ADMIN_INLINES = []
user_model = get_user_model()
admin_class = UserAdmin
for model, admin_instance in site._registry.items():
if model == user_model:
admin_class = admin_instance.__class__
class TabularInline(admin.TabularInline):
pass
class PagePermissionInlineAdmin(TabularInline):
model = PagePermission
# use special form, so we can override of user and group field
form = PagePermissionInlineAdminForm
classes = ['collapse', 'collapsed']
extra = 0 # edit page load time boost
show_with_view_permissions = False
def has_change_permission(self, request, obj=None):
if not obj:
return False
return page_permissions.user_can_change_page_permissions(
request.user,
page=obj,
site=obj.node.site,
)
def has_add_permission(self, request, obj=None):
return self.has_change_permission(request, obj)
@classproperty
def raw_id_fields(cls):
# Dynamically set raw_id_fields based on settings
threshold = get_cms_setting('RAW_ID_USERS')
# Given a fresh django-cms install and a django settings with the
# CMS_RAW_ID_USERS = CMS_PERMISSION = True
# django throws an OperationalError when running
# ./manage migrate
# because auth_user doesn't exists yet
try:
threshold = threshold and get_user_model().objects.count() > threshold
except OperationalError:
threshold = False
return ['user'] if threshold else []
def get_queryset(self, request):
"""
Queryset change, so user with global change permissions can see
all permissions. Otherwise user can see only permissions for
peoples which are under him (he can't see his permissions, because
this will lead to violation, when he can add more power to himself)
"""
site = Site.objects.get_current(request)
try:
# can see only permissions for users which are under him in tree
qs = self.model.objects.subordinate_to_user(request.user, site)
except NoPermissionsException:
return self.model.objects.none()
return qs.filter(can_view=self.show_with_view_permissions)
def get_formset(self, request, obj=None, **kwargs):
"""
Some fields may be excluded here. User can change only
permissions which are available for him. E.g. if user does not haves
can_publish flag, he can't change assign can_publish permissions.
"""
exclude = self.exclude or []
if obj:
user = request.user
if not obj.has_add_permission(user):
exclude.append('can_add')
if not obj.has_delete_permission(user):
exclude.append('can_delete')
if not obj.has_publish_permission(user):
exclude.append('can_publish')
if not obj.has_advanced_settings_permission(user):
exclude.append('can_change_advanced_settings')
if not obj.has_move_page_permission(user):
exclude.append('can_move_page')
kwargs['exclude'] = exclude
formset_cls = super(PagePermissionInlineAdmin, self).get_formset(request, obj=obj, **kwargs)
qs = self.get_queryset(request)
if obj is not None:
qs = qs.filter(page=obj)
formset_cls._queryset = qs
return formset_cls
class ViewRestrictionInlineAdmin(PagePermissionInlineAdmin):
extra = 0 # edit page load time boost
form = ViewRestrictionInlineAdminForm
verbose_name = _("View restriction")
verbose_name_plural = _("View restrictions")
show_with_view_permissions = True
class GlobalPagePermissionAdmin(admin.ModelAdmin):
list_display = ['user', 'group', 'can_change', 'can_delete', 'can_publish', 'can_change_permissions']
list_filter = ['user', 'group', 'can_change', 'can_delete', 'can_publish', 'can_change_permissions']
form = GlobalPagePermissionAdminForm
search_fields = []
for field in admin_class.search_fields:
search_fields.append("user__%s" % field)
search_fields.append('group__name')
list_display.append('can_change_advanced_settings')
list_filter.append('can_change_advanced_settings')
def get_list_filter(self, request):
threshold = get_cms_setting('RAW_ID_USERS')
try:
threshold = threshold and get_user_model().objects.count() > threshold
except OperationalError:
threshold = False
filter_copy = deepcopy(self.list_filter)
if threshold:
filter_copy.remove('user')
return filter_copy
def has_add_permission(self, request):
site = Site.objects.get_current(request)
return permissions.user_can_add_global_permissions(request.user, site)
def has_change_permission(self, request, obj=None):
site = Site.objects.get_current(request)
return permissions.user_can_change_global_permissions(request.user, site)
def has_delete_permission(self, request, obj=None):
site = Site.objects.get_current(request)
return permissions.user_can_delete_global_permissions(request.user, site)
@classproperty
def raw_id_fields(cls):
# Dynamically set raw_id_fields based on settings
threshold = get_cms_setting('RAW_ID_USERS')
# Given a fresh django-cms install and a django settings with the
# CMS_RAW_ID_USERS = CMS_PERMISSION = True
# django throws an OperationalError when running
# ./manage migrate
# because auth_user doesn't exists yet
try:
threshold = threshold and get_user_model().objects.count() > threshold
except OperationalError:
threshold = False
return ['user'] if threshold else []
if get_cms_setting('PERMISSION'):
admin.site.register(GlobalPagePermission, GlobalPagePermissionAdmin)
PERMISSION_ADMIN_INLINES.extend([
ViewRestrictionInlineAdmin,
PagePermissionInlineAdmin,
])
File diff suppressed because it is too large Load Diff
-139
View File
@@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
from functools import update_wrapper
import copy
import json
from django.conf.urls import url
from django.contrib import admin
from django.contrib.admin import ModelAdmin
from django.contrib.auth.admin import csrf_protect_m
from django.db import transaction
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest
from django.http.request import QueryDict
from django.utils.translation import override
from django.utils.six.moves.urllib.parse import urlparse
from cms.admin.forms import RequestToolbarForm
from cms.models import UserSettings
from cms.toolbar.toolbar import CMSToolbar
from cms.utils.page import get_page_from_request
from cms.utils.urlutils import admin_reverse
class SettingsAdmin(ModelAdmin):
def get_urls(self):
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
return [
url(r'^session_store/$',
self.session_store,
name='%s_%s_session_store' % info),
url(r'^cms-toolbar/$',
wrap(self.get_toolbar),
name='%s_%s_get_toolbar' % info),
url(r'^$',
wrap(self.change_view),
name='%s_%s_change' % info),
url(r'^(.+)/$',
wrap(self.change_view),
name='%s_%s_change' % info),
]
@csrf_protect_m
@transaction.atomic
def change_view(self, request, id=None):
model = self.model
try:
obj = model.objects.get(user=request.user)
except model.DoesNotExist:
return self.add_view(request)
return super(SettingsAdmin, self).change_view(request, str(obj.pk))
def session_store(self, request):
"""
either POST or GET
POST should have a settings parameter
"""
if not request.user.is_staff:
return HttpResponse(json.dumps(""),
content_type="application/json")
if request.method == "POST":
request.session['cms_settings'] = request.POST['settings']
request.session.save()
return HttpResponse(
json.dumps(request.session.get('cms_settings', '')),
content_type="application/json"
)
def get_toolbar(self, request):
form = RequestToolbarForm(request.GET or None)
if not form.is_valid():
return HttpResponseBadRequest('Invalid parameters')
form_data = form.cleaned_data
cms_path = form_data.get('cms_path') or request.path_info
origin_url = urlparse(cms_path)
attached_obj = form_data.get('attached_obj')
current_page = get_page_from_request(request, use_path=origin_url.path, clean_path=True)
if attached_obj and current_page and not (attached_obj == current_page):
return HttpResponseBadRequest('Generic object does not match current page')
data = QueryDict(query_string=origin_url.query, mutable=True)
placeholders = request.GET.getlist("placeholders[]")
if placeholders:
data.setlist('placeholders[]', placeholders)
request = copy.copy(request)
request.GET = data
request.current_page = current_page
request.toolbar = CMSToolbar(request, request_path=origin_url.path, _async=True)
request.toolbar.set_object(attached_obj or current_page)
return HttpResponse(request.toolbar.render())
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
def response_post_save_change(self, request, obj):
#
# When the user changes his language setting, we need to do two things:
# 1. Change the language-prefix for the sideframed admin view
# 2. Reload the whole window so that the new language affects the
# toolbar, etc.
#
# To do this, we first redirect the sideframe to the correct new, URL,
# but we pass a GET param 'reload_window', which instructs JS on that
# page to strip (to avoid infinite redirection loops) that param then
# reload the whole window again.
#
with override(obj.language):
post_url = admin_reverse(
'cms_usersettings_change',
args=[obj.id, ],
current_app=self.admin_site.name
)
return HttpResponseRedirect("{0}?reload_window".format(post_url))
def has_change_permission(self, request, obj=None):
if obj and obj.user == request.user:
return True
return False
def get_model_perms(self, request):
"""
Return empty perms dict thus hiding the model from admin index.
"""
return {}
admin.site.register(UserSettings, SettingsAdmin)
-12
View File
@@ -1,12 +0,0 @@
from cms.models import StaticPlaceholder
from django.contrib import admin
from cms.admin.placeholderadmin import PlaceholderAdminMixin
class StaticPlaceholderAdmin(PlaceholderAdminMixin, admin.ModelAdmin):
list_display = ('get_name', 'code', 'site', 'creation_method')
search_fields = ('name', 'code',)
exclude = ('creation_method',)
list_filter = ('creation_method', 'site')
admin.site.register(StaticPlaceholder, StaticPlaceholderAdmin)
-150
View File
@@ -1,150 +0,0 @@
# -*- coding: utf-8 -*-
from copy import deepcopy
from django.contrib import admin
from django.contrib.admin import site
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.utils.translation import ugettext
from cms.admin.forms import PageUserChangeForm, PageUserGroupForm
from cms.exceptions import NoPermissionsException
from cms.models import Page, PagePermission, PageUser, PageUserGroup
from cms.utils.compat.forms import UserAdmin
from cms.utils.conf import get_cms_setting
from cms.utils.permissions import (
get_model_permission_codename,
get_subordinate_groups,
get_subordinate_users,
get_user_permission_level,
)
user_model = get_user_model()
admin_class = UserAdmin
for model, admin_instance in site._registry.items():
if model == user_model:
admin_class = admin_instance.__class__
class GenericCmsPermissionAdmin(object):
def get_subordinates(self, user, site):
raise NotImplementedError
def _has_change_permissions_permission(self, request):
"""
User is able to add/change objects only if he haves can change
permission on some page.
"""
site = Site.objects.get_current(request)
try:
get_user_permission_level(request.user, site)
except NoPermissionsException:
return False
return True
def get_form(self, request, obj=None, **kwargs):
form_class = super(GenericCmsPermissionAdmin, self).get_form(request, obj, **kwargs)
form_class._current_user = request.user
return form_class
def get_queryset(self, request):
queryset = super(GenericCmsPermissionAdmin, self).get_queryset(request)
site = Site.objects.get_current(request)
user_ids = self.get_subordinates(request.user, site).values_list('pk', flat=True)
return queryset.filter(pk__in=user_ids)
def has_add_permission(self, request):
has_model_perm = super(GenericCmsPermissionAdmin, self).has_add_permission(request)
if not has_model_perm:
return False
return self._has_change_permissions_permission(request)
def has_change_permission(self, request, obj=None):
has_model_perm = super(GenericCmsPermissionAdmin, self).has_change_permission(request, obj)
if not has_model_perm:
return False
return self._has_change_permissions_permission(request)
def has_delete_permission(self, request, obj=None):
has_model_perm = super(GenericCmsPermissionAdmin, self).has_delete_permission(request, obj)
if not has_model_perm:
return False
return self._has_change_permissions_permission(request)
def has_view_permission(self, request, obj=None):
# For django 2.1
# Default is to return True if user got `change` perm, but we have to
# get in consideration also cms permission system
return self.has_change_permission(request, obj)
class PageUserAdmin(GenericCmsPermissionAdmin, admin_class):
form = PageUserChangeForm
model = PageUser
def get_subordinates(self, user, site):
return get_subordinate_users(user, site).values_list('pk', flat=True)
def get_readonly_fields(self, request, obj=None):
fields = super(PageUserAdmin, self).get_readonly_fields(request, obj)
if not request.user.is_superuser:
# Non superusers can't set superuser status on
# their subordinates.
fields = list(fields) + ['is_superuser']
return fields
def save_model(self, request, obj, form, change):
if not change:
# By default set the staff flag to True
# when a PageUser is first created
obj.is_staff = True
# Set the created_by field to the current user
obj.created_by = request.user
super(PageUserAdmin, self).save_model(request, obj, form, change)
class PageUserGroupAdmin(GenericCmsPermissionAdmin, admin.ModelAdmin):
form = PageUserGroupForm
list_display = ('name', 'created_by')
fieldsets = [
(None, {'fields': ('name',)}),
]
def get_fieldsets(self, request, obj=None):
"""
Nobody can grant more than he haves, so check for user permissions
to Page and User model and render fieldset depending on them.
"""
fieldsets = deepcopy(self.fieldsets)
perm_models = (
(Page, ugettext('Page permissions')),
(PageUser, ugettext('User & Group permissions')),
(PagePermission, ugettext('Page permissions management')),
)
for i, perm_model in enumerate(perm_models):
fields = []
model, title = perm_model
name = model.__name__.lower()
for key in ('add', 'change', 'delete'):
perm_code = get_model_permission_codename(model, action=key)
if request.user.has_perm(perm_code):
fields.append('can_%s_%s' % (key, name))
if fields:
fieldsets.insert(2 + i, (title, {'fields': (fields,)}))
return fieldsets
def get_subordinates(self, user, site):
return get_subordinate_groups(user, site).values_list('pk', flat=True)
if get_cms_setting('PERMISSION'):
admin.site.register(PageUser, PageUserAdmin)
admin.site.register(PageUserGroup, PageUserGroupAdmin)
-532
View File
@@ -1,532 +0,0 @@
# -*- coding: utf-8 -*-
"""
Public Python API to create CMS contents.
WARNING: None of the functions defined in this module checks for permissions.
You must implement the necessary permission checks in your own code before
calling these methods!
"""
import datetime
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
from django.core.exceptions import FieldError
from django.core.exceptions import PermissionDenied
from django.core.exceptions import ValidationError
from django.db import transaction
from django.template.defaultfilters import slugify
from django.template.loader import get_template
from django.utils import six
from django.utils.translation import activate
from cms import constants
from cms.app_base import CMSApp
from cms.apphook_pool import apphook_pool
from cms.constants import TEMPLATE_INHERITANCE_MAGIC
from cms.models.pagemodel import Page
from cms.models.permissionmodels import (PageUser, PagePermission, GlobalPagePermission,
ACCESS_PAGE_AND_DESCENDANTS)
from cms.models.placeholdermodel import Placeholder
from cms.models.pluginmodel import CMSPlugin
from cms.models.titlemodels import Title
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool
from cms.utils import copy_plugins, get_current_site
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import get_language_list
from cms.utils.page import get_available_slug
from cms.utils.permissions import _thread_locals, current_user
from menus.menu_pool import menu_pool
#===============================================================================
# Helpers/Internals
#===============================================================================
def _verify_apphook(apphook, namespace):
"""
Verifies the apphook given is valid and returns the normalized form (name)
"""
apphook_pool.discover_apps()
if isinstance(apphook, CMSApp):
try:
assert apphook.__class__ in [app.__class__ for app in apphook_pool.apps.values()]
except AssertionError:
print(apphook_pool.apps.values())
raise
apphook_name = apphook.__class__.__name__
elif hasattr(apphook, '__module__') and issubclass(apphook, CMSApp):
return apphook.__name__
elif isinstance(apphook, six.string_types):
try:
assert apphook in apphook_pool.apps
except AssertionError:
print(apphook_pool.apps.values())
raise
apphook_name = apphook
else:
raise TypeError("apphook must be string or CMSApp instance")
if apphook_pool.apps[apphook_name].app_name and not namespace:
raise ValidationError('apphook with app_name must define a namespace')
return apphook_name
def _verify_plugin_type(plugin_type):
"""
Verifies the given plugin_type is valid and returns a tuple of
(plugin_model, plugin_type)
"""
if (hasattr(plugin_type, '__module__') and
issubclass(plugin_type, CMSPluginBase)):
plugin_model = plugin_type.model
assert plugin_type in plugin_pool.plugins.values()
plugin_type = plugin_type.__name__
elif isinstance(plugin_type, six.string_types):
try:
plugin_model = plugin_pool.get_plugin(plugin_type).model
except KeyError:
raise TypeError(
'plugin_type must be CMSPluginBase subclass or string'
)
else:
raise TypeError('plugin_type must be CMSPluginBase subclass or string')
return plugin_model, plugin_type
#===============================================================================
# Public API
#===============================================================================
@transaction.atomic
def create_page(title, template, language, menu_title=None, slug=None,
apphook=None, apphook_namespace=None, redirect=None, meta_description=None,
created_by='python-api', parent=None,
publication_date=None, publication_end_date=None,
in_navigation=False, soft_root=False, reverse_id=None,
navigation_extenders=None, published=False, site=None,
login_required=False, limit_visibility_in_menu=constants.VISIBILITY_ALL,
position="last-child", overwrite_url=None,
xframe_options=Page.X_FRAME_OPTIONS_INHERIT, page_title=None):
"""
Create a CMS Page and it's title for the given language
See docs/extending_cms/api_reference.rst for more info
"""
# validate template
if not template == TEMPLATE_INHERITANCE_MAGIC:
assert template in [tpl[0] for tpl in get_cms_setting('TEMPLATES')]
get_template(template)
# validate site
if not site:
site = get_current_site()
else:
assert isinstance(site, Site)
# validate language:
assert language in get_language_list(site), get_cms_setting('LANGUAGES').get(site.pk)
# validate parent
if parent:
assert isinstance(parent, Page)
assert parent.publisher_is_draft
# validate publication date
if publication_date:
assert isinstance(publication_date, datetime.date)
# validate publication end date
if publication_end_date:
assert isinstance(publication_end_date, datetime.date)
if navigation_extenders:
raw_menus = menu_pool.get_menus_by_attribute("cms_enabled", True)
menus = [menu[0] for menu in raw_menus]
assert navigation_extenders in menus
# validate menu visibility
accepted_limitations = (constants.VISIBILITY_ALL, constants.VISIBILITY_USERS, constants.VISIBILITY_ANONYMOUS)
assert limit_visibility_in_menu in accepted_limitations
# validate position
assert position in ('last-child', 'first-child', 'left', 'right')
target_node = parent.node if parent else None
# validate and normalize apphook
if apphook:
application_urls = _verify_apphook(apphook, apphook_namespace)
else:
application_urls = None
# ugly permissions hack
if created_by and isinstance(created_by, get_user_model()):
_thread_locals.user = created_by
created_by = getattr(created_by, get_user_model().USERNAME_FIELD)
else:
_thread_locals.user = None
if reverse_id:
if Page.objects.drafts().filter(reverse_id=reverse_id, node__site=site).exists():
raise FieldError('A page with the reverse_id="%s" already exist.' % reverse_id)
page = Page(
created_by=created_by,
changed_by=created_by,
publication_date=publication_date,
publication_end_date=publication_end_date,
in_navigation=in_navigation,
soft_root=soft_root,
reverse_id=reverse_id,
navigation_extenders=navigation_extenders,
template=template,
application_urls=application_urls,
application_namespace=apphook_namespace,
login_required=login_required,
limit_visibility_in_menu=limit_visibility_in_menu,
xframe_options=xframe_options,
)
page.set_tree_node(site=site, target=target_node, position=position)
page.save()
page.rescan_placeholders()
create_title(
language=language,
title=title,
page_title=page_title,
menu_title=menu_title,
slug=slug,
redirect=redirect,
meta_description=meta_description,
page=page,
overwrite_url=overwrite_url,
)
if published:
page.publish(language)
if parent and position in ('last-child', 'first-child'):
parent._clear_node_cache()
del _thread_locals.user
return page
@transaction.atomic
def create_title(language, title, page, menu_title=None, slug=None,
redirect=None, meta_description=None, parent=None,
overwrite_url=None, page_title=None, path=None):
"""
Create a title.
Parent is only used if slug=None.
See docs/extending_cms/api_reference.rst for more info
"""
# validate page
assert isinstance(page, Page)
# validate language:
assert language in get_language_list(page.node.site_id)
# set default slug:
if not slug:
base = page.get_path_for_slug(slugify(title), language)
slug = get_available_slug(page.node.site, base, language)
if overwrite_url:
path = overwrite_url.strip('/')
elif path is None:
path = page.get_path_for_slug(slug, language)
title = Title.objects.create(
language=language,
title=title,
menu_title=menu_title,
page_title=page_title,
slug=slug,
path=path,
redirect=redirect,
meta_description=meta_description,
page=page,
has_url_overwrite=bool(overwrite_url),
)
page_languages = page.get_languages()
if language not in page_languages:
page.update_languages(page_languages + [language])
return title
@transaction.atomic
def add_plugin(placeholder, plugin_type, language, position='last-child',
target=None, **data):
"""
Add a plugin to a placeholder
See docs/extending_cms/api_reference.rst for more info
"""
# validate placeholder
assert isinstance(placeholder, Placeholder)
# validate and normalize plugin type
plugin_model, plugin_type = _verify_plugin_type(plugin_type)
if target:
if position == 'last-child':
if CMSPlugin.node_order_by:
position = 'sorted-child'
new_pos = CMSPlugin.objects.filter(parent=target).count()
parent_id = target.pk
elif position == 'first-child':
new_pos = 0
if CMSPlugin.node_order_by:
position = 'sorted-child'
parent_id = target.pk
elif position == 'left':
new_pos = target.position
if CMSPlugin.node_order_by:
position = 'sorted-sibling'
parent_id = target.parent_id
elif position == 'right':
new_pos = target.position + 1
if CMSPlugin.node_order_by:
position = 'sorted-sibling'
parent_id = target.parent_id
else:
raise Exception('position not supported: %s' % position)
if position == 'last-child' or position == 'first-child':
qs = CMSPlugin.objects.filter(language=language, parent=target, position__gte=new_pos,
placeholder=placeholder)
else:
qs = CMSPlugin.objects.filter(language=language, parent=target.parent_id, position__gte=new_pos,
placeholder=placeholder)
for pl in qs:
pl.position += 1
pl.save()
else:
if position == 'last-child':
new_pos = CMSPlugin.objects.filter(language=language, parent__isnull=True, placeholder=placeholder).count()
else:
new_pos = 0
for pl in CMSPlugin.objects.filter(language=language, parent__isnull=True, position__gte=new_pos,
placeholder=placeholder):
pl.position += 1
pl.save()
parent_id = None
plugin_base = CMSPlugin(
plugin_type=plugin_type,
placeholder=placeholder,
position=new_pos,
language=language,
parent_id=parent_id,
)
plugin_base = plugin_base.add_root(instance=plugin_base)
if target:
plugin_base = plugin_base.move(target, pos=position)
plugin = plugin_model(**data)
plugin_base.set_base_attr(plugin)
plugin.save()
return plugin
def create_page_user(created_by, user,
can_add_page=True, can_view_page=True,
can_change_page=True, can_delete_page=True,
can_recover_page=True, can_add_pageuser=True,
can_change_pageuser=True, can_delete_pageuser=True,
can_add_pagepermission=True,
can_change_pagepermission=True,
can_delete_pagepermission=True, grant_all=False):
"""
Creates a page user.
See docs/extending_cms/api_reference.rst for more info
"""
from cms.admin.forms import save_permissions
if grant_all:
# just be lazy
return create_page_user(created_by, user, True, True, True, True,
True, True, True, True, True, True, True)
# validate created_by
assert isinstance(created_by, get_user_model())
data = {
'can_add_page': can_add_page,
'can_view_page': can_view_page,
'can_change_page': can_change_page,
'can_delete_page': can_delete_page,
'can_recover_page': can_recover_page,
'can_add_pageuser': can_add_pageuser,
'can_change_pageuser': can_change_pageuser,
'can_delete_pageuser': can_delete_pageuser,
'can_add_pagepermission': can_add_pagepermission,
'can_change_pagepermission': can_change_pagepermission,
'can_delete_pagepermission': can_delete_pagepermission,
}
user.is_staff = True
user.is_active = True
page_user = PageUser(created_by=created_by)
for field in [f.name for f in get_user_model()._meta.local_fields]:
setattr(page_user, field, getattr(user, field))
user.save()
page_user.save()
save_permissions(data, page_user)
return user
def assign_user_to_page(page, user, grant_on=ACCESS_PAGE_AND_DESCENDANTS,
can_add=False, can_change=False, can_delete=False,
can_change_advanced_settings=False, can_publish=False,
can_change_permissions=False, can_move_page=False,
can_recover_page=True, can_view=False,
grant_all=False, global_permission=False):
"""
Assigns given user to page, and gives him requested permissions.
See docs/extending_cms/api_reference.rst for more info
"""
grant_all = grant_all and not global_permission
data = {
'can_add': can_add or grant_all,
'can_change': can_change or grant_all,
'can_delete': can_delete or grant_all,
'can_change_advanced_settings': can_change_advanced_settings or grant_all,
'can_publish': can_publish or grant_all,
'can_change_permissions': can_change_permissions or grant_all,
'can_move_page': can_move_page or grant_all,
'can_view': can_view or grant_all,
}
page_permission = PagePermission(page=page, user=user,
grant_on=grant_on, **data)
page_permission.save()
if global_permission:
page_permission = GlobalPagePermission(
user=user, can_recover_page=can_recover_page, **data)
page_permission.save()
page_permission.sites.add(get_current_site())
return page_permission
def publish_page(page, user, language):
"""
Publish a page. This sets `page.published` to `True` and calls publish()
which does the actual publishing.
See docs/extending_cms/api_reference.rst for more info
"""
page = page.reload()
if not page.has_publish_permission(user):
raise PermissionDenied()
# Set the current_user to have the page's changed_by
# attribute set correctly.
# 'user' is a user object, but current_user() just wants the username (a string).
with current_user(user.get_username()):
page.publish(language)
return page.reload()
def publish_pages(include_unpublished=False, language=None, site=None):
"""
Create published public version of selected drafts.
"""
qs = Page.objects.drafts()
if not include_unpublished:
qs = qs.filter(title_set__published=True).distinct()
if site:
qs = qs.filter(node__site=site)
output_language = None
for i, page in enumerate(qs):
add = True
titles = page.title_set
if not include_unpublished:
titles = titles.filter(published=True)
for lang in titles.values_list("language", flat=True):
if language is None or lang == language:
if not output_language:
output_language = lang
if not page.publish(lang):
add = False
# we may need to activate the first (main) language for proper page title rendering
activate(output_language)
yield (page, add)
def get_page_draft(page):
"""
Returns the draft version of a page, regardless if the passed in
page is a published version or a draft version.
:param page: The page to get the draft version
:type page: :class:`cms.models.pagemodel.Page` instance
:return page: draft version of the page
:type page: :class:`cms.models.pagemodel.Page` instance
"""
if page:
if page.publisher_is_draft:
return page
else:
return page.publisher_public
else:
return None
def copy_plugins_to_language(page, source_language, target_language,
only_empty=True):
"""
Copy the plugins to another language in the same page for all the page
placeholders.
By default plugins are copied only if placeholder has no plugin for the
target language; use ``only_empty=False`` to change this.
.. warning: This function skips permissions checks
:param page: the page to copy
:type page: :class:`cms.models.pagemodel.Page` instance
:param string source_language: The source language code,
must be in :setting:`django:LANGUAGES`
:param string target_language: The source language code,
must be in :setting:`django:LANGUAGES`
:param bool only_empty: if False, plugin are copied even if
plugins exists in the target language (on a placeholder basis).
:return int: number of copied plugins
"""
copied = 0
placeholders = page.get_placeholders()
for placeholder in placeholders:
# only_empty is True we check if the placeholder already has plugins and
# we skip it if has some
if not only_empty or not placeholder.get_plugins(language=target_language).exists():
plugins = list(
placeholder.get_plugins(language=source_language).order_by('path'))
copied_plugins = copy_plugins.copy_plugins_to(plugins, placeholder, target_language)
copied += len(copied_plugins)
return copied
def can_change_page(request):
"""
Check whether a user has the permission to change the page.
This will work across all permission-related setting, with a unified interface
to permission checking.
"""
from cms.utils import page_permissions
user = request.user
current_page = request.current_page
if current_page:
return page_permissions.user_can_change_page(user, current_page)
site = Site.objects.get_current(request)
return page_permissions.user_can_change_all_pages(user, site)
-95
View File
@@ -1,95 +0,0 @@
# -*- coding: utf-8 -*-
class CMSApp(object):
#: list of urlconfs: example: ``_urls = ["myapp.urls"]``
_urls = []
#: list of menu classes: example: ``_menus = [MyAppMenu]``
_menus = []
#: name of the apphook (required)
name = None
#: name of the app, this enables Django namespaces support (optional)
app_name = None
#: configuration model (optional)
app_config = None
#: if set to true, apphook inherits permissions from the current page
permissions = True
#: list of application names to exclude from inheriting CMS permissions
exclude_permissions = []
def __new__(cls):
"""
We want to bind the CMSapp class to a specific AppHookConfig, but only one at a time
Checking for the runtime attribute should be a sane fix
"""
if cls.app_config:
if getattr(cls.app_config, 'cmsapp', None) and cls.app_config.cmsapp != cls:
raise RuntimeError(
'Only one AppHook per AppHookConfiguration must exists.\n'
'AppHook %s already defined for %s AppHookConfig' % (
cls.app_config.cmsapp.__name__, cls.app_config.__name__
)
)
cls.app_config.cmsapp = cls
return super(CMSApp, cls).__new__(cls)
def get_configs(self):
"""
Returns all the apphook configuration instances.
"""
raise NotImplemented('Configurable AppHooks must implement this method')
def get_config(self, namespace):
"""
Returns the apphook configuration instance linked to the given namespace
"""
raise NotImplemented('Configurable AppHooks must implement this method')
def get_config_add_url(self):
"""
Returns the url to add a new apphook configuration instance
(usually the model admin add view)
"""
raise NotImplemented('Configurable AppHooks must implement this method')
def get_menus(self, page=None, language=None, **kwargs):
"""
Returns the menus for the apphook instance, eventually selected according
to the given arguments.
By default it returns the menus assigned to :py:attr:`CMSApp._menus`.
If no menus are returned, then the user will need to attach menus to pages
manually in the admin.
This method must return all the menus used by this apphook if no arguments are
provided. Example::
if page and page.reverse_id == 'page1':
return [Menu1]
elif page and page.reverse_id == 'page2':
return [Menu2]
else:
return [Menu1, Menu2]
:param page: page the apphook is attached to
:param language: current site language
:return: list of menu classes
"""
return self._menus
def get_urls(self, page=None, language=None, **kwargs):
"""
Returns the urlconfs for the apphook instance, eventually selected
according to the given arguments.
By default it returns the urls assigned to :py:attr:`CMSApp._urls`
This method **must** return a non empty list of urlconfs,
even if no argument is passed.
:param page: page the apphook is attached to
:param language: current site language
:return: list of urlconfs strings
"""
return self._urls
-102
View File
@@ -1,102 +0,0 @@
# -*- coding: utf-8 -*-
import warnings
from django.core.exceptions import ImproperlyConfigured
from django.utils.module_loading import autodiscover_modules, import_string
from django.utils.translation import ugettext as _
from cms.app_base import CMSApp
from cms.exceptions import AppAlreadyRegistered
from cms.utils.conf import get_cms_setting
class ApphookPool(object):
def __init__(self):
self.apphooks = []
self.apps = {}
self.discovered = False
def clear(self):
# TODO: remove this method, it's Python, we don't need it.
self.apphooks = []
self.apps = {}
self.discovered = False
def register(self, app=None, discovering_apps=False):
# allow use as a decorator
if app is None:
return lambda app: self.register(app, discovering_apps)
if self.apphooks and not discovering_apps:
return app
if app.__name__ in self.apps:
raise AppAlreadyRegistered(
'A CMS application %r is already registered' % app.__name__)
if not issubclass(app, CMSApp):
raise ImproperlyConfigured(
'CMS application must inherit from cms.app_base.CMSApp, '
'but %r does not' % app.__name__)
if not hasattr(app, 'menus') and hasattr(app, 'menu'):
warnings.warn("You define a 'menu' attribute on CMS application "
"%r, but the 'menus' attribute is empty, "
"did you make a typo?" % app.__name__)
self.apps[app.__name__] = app()
return app
def discover_apps(self):
self.apphooks = get_cms_setting('APPHOOKS')
if self.apphooks:
for path in self.apphooks:
cls = import_string(path)
try:
self.register(cls, discovering_apps=True)
except AppAlreadyRegistered:
pass
else:
autodiscover_modules('cms_apps')
self.discovered = True
def get_apphooks(self):
hooks = []
if not self.discovered:
self.discover_apps()
for app_name in self.apps:
app = self.apps[app_name]
if app.get_urls():
hooks.append((app_name, app.name))
# Unfortunately, we lose the ordering since we now have a list of
# tuples. Let's reorder by app_name:
hooks = sorted(hooks, key=lambda hook: hook[1])
return hooks
def get_apphook(self, app_name):
if not self.discovered:
self.discover_apps()
try:
return self.apps[app_name]
except KeyError:
# deprecated: return apphooks registered in db with urlconf name
# instead of apphook class name
for app in self.apps.values():
if app_name in app.get_urls():
return app
warnings.warn(_('No registered apphook "%r" found') % app_name)
return None
apphook_pool = ApphookPool()
-273
View File
@@ -1,273 +0,0 @@
# -*- coding: utf-8 -*-
from collections import OrderedDict
from importlib import import_module
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.db import OperationalError, ProgrammingError
from django.utils import six
from django.utils.translation import get_language, override
from django.urls import Resolver404, reverse
from cms.apphook_pool import apphook_pool
from cms.models.pagemodel import Page
from cms.utils import get_current_site
from cms.utils.compat import DJANGO_1_11
from cms.utils.compat.dj import RegexPattern, URLPattern, URLResolver
from cms.utils.i18n import get_language_list
from cms.utils.moderator import use_draft
APP_RESOLVERS = []
def clear_app_resolvers():
global APP_RESOLVERS
APP_RESOLVERS = []
def applications_page_check(request, current_page=None, path=None):
"""Tries to find if given path was resolved over application.
Applications have higher priority than other cms pages.
"""
if current_page:
return current_page
if path is None:
# We should get in this branch only if an apphook is active on /
# This removes the non-CMS part of the URL.
path = request.path_info.replace(reverse('pages-root'), '', 1)
# check if application resolver can resolve this
for lang in get_language_list():
if path.startswith(lang + "/"):
path = path[len(lang + "/"):]
use_public = not use_draft(request)
for resolver in APP_RESOLVERS:
try:
page_id = resolver.resolve_page_id(path)
# yes, it is application page
page = Page.objects.public().get(id=page_id)
# If current page was matched, then we have some override for
# content from cms, but keep current page. Otherwise return page
# to which was application assigned.
return page if use_public else page.publisher_public
except Resolver404:
# Raised if the page is not managed by an apphook
pass
except Page.DoesNotExist:
pass
return None
class AppRegexURLResolver(URLResolver):
def __init__(self, *args, **kwargs):
self.page_id = None
self.url_patterns_dict = {}
super(AppRegexURLResolver, self).__init__(*args, **kwargs)
@property
def url_patterns(self):
language = get_language()
if language in self.url_patterns_dict:
return self.url_patterns_dict[language]
else:
return []
def resolve_page_id(self, path):
"""Resolves requested path similar way how resolve does, but instead
of return callback,.. returns page_id to which was application
assigned.
"""
tried = []
pattern = getattr(self, 'pattern', self)
match = pattern.regex.search(path)
if match:
new_path = path[match.end():]
for pattern in self.url_patterns:
if isinstance(pattern, AppRegexURLResolver):
try:
return pattern.resolve_page_id(new_path)
except Resolver404:
pass
else:
try:
sub_match = pattern.resolve(new_path)
except Resolver404 as e:
tried_match = e.args[0].get('tried')
if tried_match is not None:
tried.extend([[pattern] + t for t in tried_match])
else:
tried.extend([pattern])
else:
if sub_match:
return getattr(pattern, 'page_id', None)
pattern = getattr(pattern, 'pattern', pattern)
tried.append(pattern.regex.pattern)
raise Resolver404({'tried': tried, 'path': new_path})
def recurse_patterns(path, pattern_list, page_id, default_args=None,
nested=False):
"""
Recurse over a list of to-be-hooked patterns for a given path prefix
"""
newpatterns = []
for pattern in pattern_list:
app_pat = getattr(pattern, 'pattern', pattern).regex.pattern
# make sure we don't get patterns that start with more than one '^'!
app_pat = app_pat.lstrip('^')
path = path.lstrip('^')
regex = r'^%s%s' % (path, app_pat) if not nested else r'^%s' % (app_pat)
if isinstance(pattern, URLResolver):
# include default_args
args = pattern.default_kwargs
if default_args:
args.update(default_args)
# see lines 243 and 236 of urlresolvers.py to understand the next line
urlconf_module = recurse_patterns(regex, pattern.url_patterns, page_id, args, nested=True)
# this is an 'include', recurse!
regex_pattern = regex
if not DJANGO_1_11:
regex_pattern = RegexPattern(regex)
resolver = URLResolver(regex_pattern, urlconf_module,
pattern.default_kwargs, pattern.app_name,
pattern.namespace)
else:
# Re-do the URLPattern with the new regular expression
args = pattern.default_args
if default_args:
args.update(default_args)
regex_pattern = regex
if not DJANGO_1_11:
regex_pattern = RegexPattern(regex, name=pattern.name)
resolver = URLPattern(regex_pattern, pattern.callback, args,
pattern.name)
resolver.page_id = page_id
newpatterns.append(resolver)
return newpatterns
def _set_permissions(patterns, exclude_permissions):
for pattern in patterns:
if isinstance(pattern, URLResolver):
if pattern.namespace in exclude_permissions:
continue
_set_permissions(pattern.url_patterns, exclude_permissions)
else:
from cms.utils.decorators import cms_perms
pattern.callback = cms_perms(pattern.callback)
def get_app_urls(urls):
for urlconf in urls:
if isinstance(urlconf, six.string_types):
mod = import_module(urlconf)
if not hasattr(mod, 'urlpatterns'):
raise ImproperlyConfigured(
"URLConf `%s` has no urlpatterns attribute" % urlconf)
yield getattr(mod, 'urlpatterns')
elif isinstance(urlconf, (list, tuple)):
yield urlconf
else:
yield [urlconf]
def get_patterns_for_title(path, title):
"""
Resolve the urlconf module for a path+title combination
Returns a list of url objects.
"""
app = apphook_pool.get_apphook(title.page.application_urls)
url_patterns = []
for pattern_list in get_app_urls(app.get_urls(title.page, title.language)):
if path and not path.endswith('/'):
path += '/'
page_id = title.page.id
url_patterns += recurse_patterns(path, pattern_list, page_id)
return url_patterns
def get_app_patterns():
try:
site = get_current_site()
return _get_app_patterns(site)
except (OperationalError, ProgrammingError):
# ignore if DB is not ready
# Starting with Django 1.9 this code gets called even when creating
# or running migrations. So in many cases the DB will not be ready yet.
return []
def _get_app_patterns(site):
"""
Get a list of patterns for all hooked apps.
How this works:
By looking through all titles with an app hook (application_urls) we find
all urlconf modules we have to hook into titles.
If we use the ML URL Middleware, we namespace those patterns with the title
language.
All 'normal' patterns from the urlconf get re-written by prefixing them with
the title path and then included into the cms url patterns.
If the app is still configured, but is no longer installed/available, then
this method returns a degenerate patterns object: patterns('')
"""
from cms.models import Title
included = []
# we don't have a request here so get_page_queryset() can't be used,
# so use public() queryset.
# This can be done because url patterns are used just in frontend
title_qs = Title.objects.public().filter(page__node__site=site)
hooked_applications = OrderedDict()
# Loop over all titles with an application hooked to them
titles = (title_qs.exclude(page__application_urls=None)
.exclude(page__application_urls='')
.order_by('-page__node__path').select_related())
# TODO: Need to be fixed for django-treebeard when forward ported to 3.1
for title in titles:
path = title.path
mix_id = "%s:%s:%s" % (
path + "/", title.page.application_urls, title.language)
if mix_id in included:
# don't add the same thing twice
continue
if not settings.APPEND_SLASH:
path += '/'
app = apphook_pool.get_apphook(title.page.application_urls)
if not app:
continue
if title.page_id not in hooked_applications:
hooked_applications[title.page_id] = {}
app_ns = app.app_name, title.page.application_namespace
with override(title.language):
hooked_applications[title.page_id][title.language] = (
app_ns, get_patterns_for_title(path, title), app)
included.append(mix_id)
# Build the app patterns to be included in the cms urlconfs
app_patterns = []
for page_id in hooked_applications.keys():
resolver = None
for lang in hooked_applications[page_id].keys():
(app_ns, inst_ns), current_patterns, app = hooked_applications[page_id][lang] # nopyflakes
if not resolver:
regex_pattern = RegexPattern(r'') if not DJANGO_1_11 else r''
resolver = AppRegexURLResolver(
regex_pattern, 'app_resolver', app_name=app_ns, namespace=inst_ns)
resolver.page_id = page_id
if app.permissions:
_set_permissions(current_patterns, app.exclude_permissions)
resolver.url_patterns_dict[lang] = current_patterns
app_patterns.append(resolver)
APP_RESOLVERS.append(resolver)
return app_patterns
-12
View File
@@ -1,12 +0,0 @@
from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _
class CMSConfig(AppConfig):
name = 'cms'
verbose_name = _("django CMS")
def ready(self):
from cms.utils.setup import setup
setup()
-87
View File
@@ -1,87 +0,0 @@
# -*- coding: utf-8 -*-
import re
from cms.utils.conf import get_cms_setting
CMS_PAGE_CACHE_VERSION_KEY = get_cms_setting("CACHE_PREFIX") + '_PAGE_CACHE_VERSION'
def _get_cache_version():
"""
Returns the current page cache version, explicitly setting one if not
defined.
"""
from django.core.cache import cache
version = cache.get(CMS_PAGE_CACHE_VERSION_KEY)
if version:
return version
else:
_set_cache_version(1)
return 1
def _set_cache_version(version):
"""
Set the cache version to the specified value.
"""
from django.core.cache import cache
cache.set(
CMS_PAGE_CACHE_VERSION_KEY,
version,
get_cms_setting('CACHE_DURATIONS')['content']
)
def invalidate_cms_page_cache():
"""
Invalidates the CMS PAGE CACHE.
"""
#
# NOTE: We're using a cache versioning strategy for invalidating the page
# cache when necessary. Instead of wiping all the old entries, we simply
# increment the version number rendering all previous entries
# inaccessible and left to expire naturally.
#
# ALSO NOTE: According to the Django documentation, a timeout value of
# `None' (in version 1.6+) is supposed to mean "cache forever", however,
# this is actually only implemented as only slightly less than 30 days in
# some backends (memcached, in particular). In older Djangos, `None' means
# "use default value". To avoid issues arising from different Django
# versions and cache backend implementations, we will explicitly set the
# lifespan of the CMS_PAGE_CACHE_VERSION entry to whatever is set in
# settings.CACHE_DURATIONS['content']. This allows users to adjust as
# necessary for their backend.
#
# To prevent writing cache entries that will live longer than our version
# key, we will always re-write the current version number into the cache
# just after we write any new cache entries, thus ensuring that the
# version number will always outlive any entries written against that
# version. This is a cheap operation.
#
# If there are no new cache writes before the version key expires, its
# perfectly OK, since any previous entries cached against that version
# will have also expired, so, it'd be pointless to try to access them
# anyway.
#
version = _get_cache_version()
_set_cache_version(version + 1)
CLEAN_KEY_PATTERN = re.compile(r'[^a-zA-Z0-9_-]')
def _clean_key(key):
return CLEAN_KEY_PATTERN.sub('-', key)
def _get_cache_key(name, page_lookup, lang, site_id):
from cms.models import Page
if isinstance(page_lookup, Page):
page_key = str(page_lookup.pk)
else:
page_key = str(page_lookup)
page_key = _clean_key(page_key)
return get_cms_setting('CACHE_PREFIX') + name + '__page_lookup:' + page_key + '_site:' + str(site_id) + '_lang:' + str(lang)
-31
View File
@@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from cms.utils.conf import get_cms_setting
def _site_cache_key(lang):
return "%s-%s" %(get_cms_setting('SITE_CHOICES_CACHE_KEY'), lang)
def _page_cache_key(lang):
return "%s-%s" %(get_cms_setting('PAGE_CHOICES_CACHE_KEY'), lang)
def _clean_many(prefix):
from django.core.cache import cache
keys = []
if settings.USE_I18N:
for lang in [language[0] for language in settings.LANGUAGES]:
keys.append("%s-%s" %(prefix, lang))
else:
keys = ["%s-%s" %(prefix, settings.LANGUAGE_CODE)]
cache.delete_many(keys)
def clean_site_choices_cache(sender, **kwargs):
_clean_many(get_cms_setting('SITE_CHOICES_CACHE_KEY'))
def clean_page_choices_cache(sender, **kwargs):
_clean_many(get_cms_setting('PAGE_CHOICES_CACHE_KEY'))
-129
View File
@@ -1,129 +0,0 @@
# -*- coding: utf-8 -*-
import hashlib
from datetime import timedelta
from django.conf import settings
from django.utils.cache import add_never_cache_headers, patch_response_headers, patch_vary_headers
from django.utils.encoding import iri_to_uri
from django.utils.timezone import now
from cms.cache import _get_cache_version, _set_cache_version, _get_cache_key
from cms.constants import EXPIRE_NOW, MAX_EXPIRATION_TTL
from cms.toolbar.utils import get_toolbar_from_request
from cms.utils.conf import get_cms_setting
from cms.utils.helpers import get_timezone_name
def _page_cache_key(request):
#sha1 key of current path
cache_key = "%s:%d:%s" % (
get_cms_setting("CACHE_PREFIX"),
settings.SITE_ID,
hashlib.sha1(iri_to_uri(request.get_full_path()).encode('utf-8')).hexdigest()
)
if settings.USE_TZ:
cache_key += '.%s' % get_timezone_name()
return cache_key
def set_page_cache(response):
from django.core.cache import cache
request = response._request
toolbar = get_toolbar_from_request(request)
is_authenticated = request.user.is_authenticated
if is_authenticated or toolbar._cache_disabled or not get_cms_setting("PAGE_CACHE"):
add_never_cache_headers(response)
return response
# This *must* be TZ-aware
timestamp = now()
placeholders = toolbar.content_renderer.get_rendered_placeholders()
# Checks if there's a plugin using the legacy "cache = False"
placeholder_ttl_list = []
vary_cache_on_set = set()
for ph in placeholders:
# get_cache_expiration() always returns:
# EXPIRE_NOW <= int <= MAX_EXPIRATION_IN_SECONDS
ttl = ph.get_cache_expiration(request, timestamp)
vary_cache_on = ph.get_vary_cache_on(request)
placeholder_ttl_list.append(ttl)
if ttl and vary_cache_on:
# We're only interested in vary headers if they come from
# a cache-able placeholder.
vary_cache_on_set |= set(vary_cache_on)
if EXPIRE_NOW not in placeholder_ttl_list:
if placeholder_ttl_list:
min_placeholder_ttl = min(x for x in placeholder_ttl_list)
else:
# Should only happen when there are no placeholders at all
min_placeholder_ttl = MAX_EXPIRATION_TTL
ttl = min(
get_cms_setting('CACHE_DURATIONS')['content'],
min_placeholder_ttl
)
if ttl > 0:
# Adds expiration, etc. to headers
patch_response_headers(response, cache_timeout=ttl)
patch_vary_headers(response, sorted(vary_cache_on_set))
version = _get_cache_version()
# We also store the absolute expiration timestamp to avoid
# recomputing it on cache-reads.
expires_datetime = timestamp + timedelta(seconds=ttl)
cache.set(
_page_cache_key(request),
(
response.content,
response._headers,
expires_datetime,
),
ttl,
version=version
)
# See note in invalidate_cms_page_cache()
_set_cache_version(version)
return response
def get_page_cache(request):
from django.core.cache import cache
return cache.get(_page_cache_key(request), version=_get_cache_version())
def get_xframe_cache(page):
from django.core.cache import cache
return cache.get('cms:xframe_options:%s' % page.pk)
def set_xframe_cache(page, xframe_options):
from django.core.cache import cache
cache.set('cms:xframe_options:%s' % page.pk,
xframe_options,
version=_get_cache_version())
_set_cache_version(_get_cache_version())
def _page_url_key(page_lookup, lang, site_id):
return _get_cache_key('page_url', page_lookup, lang, site_id) + '_type:absolute_url'
def set_page_url_cache(page_lookup, lang, site_id, url):
from django.core.cache import cache
cache.set(_page_url_key(page_lookup, lang, site_id),
url,
get_cms_setting('CACHE_DURATIONS')['content'], version=_get_cache_version())
_set_cache_version(_get_cache_version())
def get_page_url_cache(page_lookup, lang, site_id):
from django.core.cache import cache
return cache.get(_page_url_key(page_lookup, lang, site_id),
version=_get_cache_version())
-70
View File
@@ -1,70 +0,0 @@
# -*- coding: utf-8 -*-
from django.contrib.auth import get_user_model
from cms.utils.conf import get_cms_setting
PERMISSION_KEYS = [
'add_page', 'change_page', 'change_page_advanced_settings',
'change_page_permissions', 'delete_page', 'move_page',
'publish_page', 'view_page',
]
def get_cache_key(user, key):
username = getattr(user, get_user_model().USERNAME_FIELD)
return "%s:permission:%s:%s" % (
get_cms_setting('CACHE_PREFIX'), username, key)
def get_cache_permission_version_key():
return "%s:permission:version" % (get_cms_setting('CACHE_PREFIX'),)
def get_cache_permission_version():
from django.core.cache import cache
try:
version = int(cache.get(get_cache_permission_version_key()))
except Exception:
version = 1
return int(version)
def get_permission_cache(user, key):
"""
Helper for reading values from cache
"""
from django.core.cache import cache
return cache.get(get_cache_key(user, key), version=get_cache_permission_version())
def set_permission_cache(user, key, value):
"""
Helper method for storing values in cache. Stores used keys so
all of them can be cleaned when clean_permission_cache gets called.
"""
from django.core.cache import cache
# store this key, so we can clean it when required
cache_key = get_cache_key(user, key)
cache.set(cache_key, value,
get_cms_setting('CACHE_DURATIONS')['permissions'],
version=get_cache_permission_version())
def clear_user_permission_cache(user):
"""
Cleans permission cache for given user.
"""
from django.core.cache import cache
for key in PERMISSION_KEYS:
cache.delete(get_cache_key(user, key), version=get_cache_permission_version())
def clear_permission_cache():
from django.core.cache import cache
version = get_cache_permission_version()
if version > 1:
cache.incr(get_cache_permission_version_key())
else:
cache.set(get_cache_permission_version_key(), 2,
get_cms_setting('CACHE_DURATIONS')['permissions'])
-178
View File
@@ -1,178 +0,0 @@
# -*- coding: utf-8 -*-
"""
This module manages placeholder caching. We use a cache-versioning strategy
in which each (placeholder x lang x site_id) manages its own version. The
actual cache includes additional keys appropriate for the placeholders
get_vary_cache_on().
Invalidation of a placeholder's cache simply increments the version number for
the (placeholder x lang) pair, which renders any cache entries for that
placeholder under that version inaccessible. Those cache entries will simply
expire and will be purged according to the policy of the cache backend in-use.
The cache entries themselves may include additional sub-keys, according to the
list of VARY header-names as returned by placeholder.get_vary_cache_on() and
the current HTTPRequest object.
The vary-on header-names are also stored with the version. This enables us to
check for cache hits without re-computing placeholder.get_vary_cache_on().
"""
import hashlib
import time
from django.utils.timezone import now
from cms.utils.conf import get_cms_setting
from cms.utils.helpers import get_header_name, get_timezone_name
def _get_placeholder_cache_version_key(placeholder, lang, site_id):
"""
Returns the version key for the given «placeholder», «lang» and «site_id».
Invalidating this (via clear_placeholder_cache by replacing the stored
value with a new value) will effectively make all "sub-caches" relating to
this (placeholder x lang) inaccessible. Sub-caches include caches per TZ
and per VARY header.
"""
prefix = get_cms_setting('CACHE_PREFIX')
key = '{prefix}|placeholder_cache_version|id:{id}|lang:{lang}|site:{site}'.format(
prefix=prefix,
id=placeholder.pk,
lang=str(lang),
site=site_id,
)
if len(key) > 250:
key = '{prefix}|{hash}'.format(
prefix=prefix,
hash=hashlib.sha1(key.encode('utf-8')).hexdigest(),
)
return key
def _get_placeholder_cache_version(placeholder, lang, site_id):
"""
Gets the (placeholder x lang)'s current version and vary-on header-names
list, if present, otherwise resets to («timestamp», []).
"""
from django.core.cache import cache
key = _get_placeholder_cache_version_key(placeholder, lang, site_id)
cached = cache.get(key)
if cached:
version, vary_on_list = cached
else:
version = int(time.time() * 1000000)
vary_on_list = []
_set_placeholder_cache_version(placeholder, lang, site_id, version, vary_on_list)
return version, vary_on_list
def _set_placeholder_cache_version(placeholder, lang, site_id, version, vary_on_list=None, duration=None):
"""
Sets the (placeholder x lang)'s version and vary-on header-names list.
"""
from django.core.cache import cache
key = _get_placeholder_cache_version_key(placeholder, lang, site_id)
if not version or version < 1:
version = int(time.time() * 1000000)
if vary_on_list is None:
vary_on_list = []
cache.set(key, (version, vary_on_list), duration)
def _get_placeholder_cache_key(placeholder, lang, site_id, request, soft=False):
"""
Returns the fully-addressed cache key for the given placeholder and
the request.
The kwarg «soft» should be set to True if getting the cache key to then
read from the cache. If instead the key retrieval is to support a cache
write, let «soft» be False.
"""
prefix = get_cms_setting('CACHE_PREFIX')
version, vary_on_list = _get_placeholder_cache_version(placeholder, lang, site_id)
main_key = '{prefix}|render_placeholder|id:{id}|lang:{lang}|site:{site}|tz:{tz}|v:{version}'.format(
prefix=prefix,
id=placeholder.pk,
lang=lang,
site=site_id,
tz=get_timezone_name(),
version=version,
)
if not soft:
# We are about to write to the cache, so we want to get the latest
# vary_cache_on headers and the correct cache expiration, ignoring any
# we already have. If the placeholder has already been rendered, this
# will be very efficient (zero-additional queries) due to the caching
# of all its plugins during the rendering process anyway.
vary_on_list = placeholder.get_vary_cache_on(request)
duration = placeholder.get_cache_expiration(request, now())
# Update the main placeholder cache version
_set_placeholder_cache_version(
placeholder, lang, site_id, version, vary_on_list, duration)
sub_key_list = []
for key in vary_on_list:
value = request.META.get(get_header_name(key)) or '_'
sub_key_list.append(key + ':' + value)
cache_key = main_key
if sub_key_list:
cache_key += '|' + '|'.join(sub_key_list)
if len(cache_key) > 250:
cache_key = '{prefix}|{hash}'.format(
prefix=prefix,
hash=hashlib.sha1(cache_key.encode('utf-8')).hexdigest(),
)
return cache_key
def set_placeholder_cache(placeholder, lang, site_id, content, request):
"""
Sets the (correct) placeholder cache with the rendered placeholder.
"""
from django.core.cache import cache
key = _get_placeholder_cache_key(placeholder, lang, site_id, request)
duration = min(
get_cms_setting('CACHE_DURATIONS')['content'],
placeholder.get_cache_expiration(request, now())
)
cache.set(key, content, duration)
# "touch" the cache-version, so that it stays as fresh as this content.
version, vary_on_list = _get_placeholder_cache_version(placeholder, lang, site_id)
_set_placeholder_cache_version(
placeholder, lang, site_id, version, vary_on_list, duration=duration)
def get_placeholder_cache(placeholder, lang, site_id, request):
"""
Returns the placeholder from cache respecting the placeholder's
VARY headers.
"""
from django.core.cache import cache
key = _get_placeholder_cache_key(placeholder, lang, site_id, request, soft=True)
content = cache.get(key)
return content
def clear_placeholder_cache(placeholder, lang, site_id):
"""
Invalidates all existing cache entries for (placeholder x lang x site_id).
We don't need to re-store the vary_on_list, because the cache is now
effectively empty.
"""
version = int(time.time() * 1000000)
_set_placeholder_cache_version(placeholder, lang, site_id, version, [])
-489
View File
@@ -1,489 +0,0 @@
# -*- coding: utf-8 -*-
from django.db.models.query import Prefetch, prefetch_related_objects
from django.urls import reverse
from django.utils.functional import SimpleLazyObject
from django.utils.translation import override as force_language
from cms import constants
from cms.api import get_page_draft
from cms.apphook_pool import apphook_pool
from cms.models import EmptyTitle
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import (
get_fallback_languages,
get_public_languages,
hide_untranslated,
is_valid_site_language,
)
from cms.utils.permissions import get_view_restrictions
from cms.utils.page import get_page_queryset
from cms.utils.page_permissions import user_can_view_all_pages
from menus.base import Menu, NavigationNode, Modifier
from menus.menu_pool import menu_pool
def get_visible_nodes(request, pages, site):
"""
This code is basically a many-pages-at-once version of
cms.utils.page_permissions.user_can_view_page
pages contains all published pages
"""
user = request.user
_get_page_draft = get_page_draft
public_for = get_cms_setting('PUBLIC_FOR')
can_see_unrestricted = public_for == 'all' or (public_for == 'staff' and user.is_staff)
if not user.is_authenticated and not can_see_unrestricted:
# User is not authenticated and can't see unrestricted pages,
# no need to check for page restrictions because if there's some,
# user is anon and if there is not any, user can't see unrestricted.
return []
if user_can_view_all_pages(user, site):
return list(pages)
# Permissions are only attached to draft pages
draft_pages = [_get_page_draft(page) for page in pages]
restricted_pages = get_view_restrictions(draft_pages)
if not restricted_pages:
# If there's no restrictions, let the user see all pages
# only if he can see unrestricted, otherwise return no pages.
return list(pages) if can_see_unrestricted else []
user_id = user.pk
user_groups = SimpleLazyObject(lambda: frozenset(user.groups.values_list('pk', flat=True)))
is_auth_user = user.is_authenticated
def user_can_see_page(page):
page_id = page.pk if page.publisher_is_draft else page.publisher_public_id
page_permissions = restricted_pages.get(page_id, [])
if not page_permissions:
# Page has no view restrictions, fallback to the project's
# CMS_PUBLIC_FOR setting.
return can_see_unrestricted
if not is_auth_user:
return False
for perm in page_permissions:
if perm.user_id == user_id or perm.group_id in user_groups:
return True
return False
return [page for page in pages if user_can_see_page(page)]
def get_menu_node_for_page(renderer, page, language, fallbacks=None):
"""
Transform a CMS page into a navigation node.
:param renderer: MenuRenderer instance bound to the request
:param page: the page you wish to transform
:param language: The current language used to render the menu
"""
if fallbacks is None:
fallbacks = []
# Theses are simple to port over, since they are not calculated.
# Other attributes will be added conditionally later.
attr = {
'is_page': True,
'soft_root': page.soft_root,
'auth_required': page.login_required,
'reverse_id': page.reverse_id,
}
if page.limit_visibility_in_menu is constants.VISIBILITY_ALL:
attr['visible_for_authenticated'] = True
attr['visible_for_anonymous'] = True
else:
attr['visible_for_authenticated'] = page.limit_visibility_in_menu == constants.VISIBILITY_USERS
attr['visible_for_anonymous'] = page.limit_visibility_in_menu == constants.VISIBILITY_ANONYMOUS
attr['is_home'] = page.is_home
# Extenders can be either navigation extenders or from apphooks.
extenders = []
if page.navigation_extenders:
if page.navigation_extenders in renderer.menus:
extenders.append(page.navigation_extenders)
elif "{0}:{1}".format(page.navigation_extenders, page.pk) in renderer.menus:
extenders.append("{0}:{1}".format(page.navigation_extenders, page.pk))
# Is this page an apphook? If so, we need to handle the apphooks's nodes
# Only run this if we have a translation in the requested language for this
# object. The title cache should have been prepopulated in CMSMenu.get_nodes
# but otherwise, just request the title normally
if page.title_cache.get(language) and page.application_urls:
# it means it is an apphook
app = apphook_pool.get_apphook(page.application_urls)
if app:
extenders += app.get_menus(page, language)
exts = []
for ext in extenders:
if hasattr(ext, "get_instances"):
# CMSAttachMenus are treated a bit differently to allow them to be
# able to be attached to multiple points in the navigation.
exts.append("{0}:{1}".format(ext.__name__, page.pk))
elif hasattr(ext, '__name__'):
exts.append(ext.__name__)
else:
exts.append(ext)
if exts:
attr['navigation_extenders'] = exts
for lang in [language] + fallbacks:
translation = page.title_cache[lang]
if translation:
# Do we have a redirectURL?
attr['redirect_url'] = translation.redirect # save redirect URL if any
# Now finally, build the NavigationNode object and return it.
# The parent_id is manually set by the menu get_nodes method.
ret_node = CMSNavigationNode(
title=translation.menu_title or translation.title,
url='',
id=page.pk,
attr=attr,
visible=page.in_navigation,
path=translation.path or translation.slug,
language=(translation.language if translation.language != language else None),
)
return ret_node
else:
raise RuntimeError('Unable to render cms menu. There is a language misconfiguration.')
class CMSNavigationNode(NavigationNode):
def __init__(self, *args, **kwargs):
self.path = kwargs.pop('path')
# language is only used when we're dealing with a fallback
self.language = kwargs.pop('language', None)
super(CMSNavigationNode, self).__init__(*args, **kwargs)
def is_selected(self, request):
try:
page_id = request.current_page.pk
except AttributeError:
return False
return page_id == self.id
def _get_absolute_url(self):
if self.attr['is_home']:
return reverse('pages-root')
return reverse('pages-details-by-slug', kwargs={"slug": self.path})
def get_absolute_url(self):
if self.language:
with force_language(self.language):
return self._get_absolute_url()
return self._get_absolute_url()
class CMSMenu(Menu):
def get_nodes(self, request):
from cms.models import Title
site = self.renderer.site
lang = self.renderer.request_language
pages = get_page_queryset(
site,
draft=self.renderer.draft_mode_active,
published=not self.renderer.draft_mode_active,
)
if is_valid_site_language(lang, site_id=site.pk):
_valid_language = True
_hide_untranslated = hide_untranslated(lang, site.pk)
else:
_valid_language = False
_hide_untranslated = False
if _valid_language:
# The request language has been explicitly configured
# for the current site.
if _hide_untranslated:
fallbacks = []
else:
fallbacks = get_fallback_languages(lang, site_id=site.pk)
languages = [lang] + [_lang for _lang in fallbacks if _lang != lang]
else:
# The request language is not configured for the current site.
# Fallback to all configured public languages for the current site.
languages = get_public_languages(site.pk)
fallbacks = languages
pages = (
pages
.filter(title_set__language__in=languages)
.select_related('node')
.order_by('node__path')
.distinct()
)
if not self.renderer.draft_mode_active:
# we're dealing with public pages.
# prefetch the draft versions.
pages = pages.select_related('publisher_public__node')
pages = get_visible_nodes(request, pages, site)
if not pages:
return []
try:
homepage = [page for page in pages if page.is_home][0]
except IndexError:
homepage = None
titles = Title.objects.filter(
language__in=languages,
publisher_is_draft=self.renderer.draft_mode_active,
)
lookup = Prefetch(
'title_set',
to_attr='filtered_translations',
queryset=titles,
)
prefetch_related_objects(pages, lookup)
# Build the blank title instances only once
blank_title_cache = {language: EmptyTitle(language=language) for language in languages}
if lang not in blank_title_cache:
blank_title_cache[lang] = EmptyTitle(language=lang)
# Maps a node id to its page id
node_id_to_page = {}
def _page_to_node(page):
# EmptyTitle is used to prevent the cms from trying
# to find a translation in the database
page.title_cache = blank_title_cache.copy()
for trans in page.filtered_translations:
page.title_cache[trans.language] = trans
menu_node = get_menu_node_for_page(
self.renderer,
page,
language=lang,
fallbacks=fallbacks,
)
return menu_node
menu_nodes = []
for page in pages:
node = page.node
parent_id = node_id_to_page.get(node.parent_id)
if node.parent_id and not parent_id:
# If the parent page is not available (unpublished, etc..)
# don't bother creating menu nodes for its descendants.
continue
menu_node = _page_to_node(page)
cut_homepage = homepage and not homepage.in_navigation
if cut_homepage and parent_id == homepage.pk:
# When the homepage is hidden from navigation,
# we need to cut all its direct children from it.
menu_node.parent_id = None
else:
menu_node.parent_id = parent_id
node_id_to_page[node.pk] = page.pk
menu_nodes.append(menu_node)
return menu_nodes
menu_pool.register_menu(CMSMenu)
class NavExtender(Modifier):
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
if post_cut:
return nodes
# rearrange the parent relations
# Find home
home = next((n for n in nodes if n.attr.get("is_home", False)), None)
# Find nodes with NavExtenders
exts = []
for node in nodes:
extenders = node.attr.get("navigation_extenders", None)
if extenders:
for ext in extenders:
if ext not in exts:
exts.append(ext)
# Link the nodes
for extnode in nodes:
if extnode.namespace == ext and not extnode.parent_id:
# if home has nav extenders but home is not visible
if node == home and not node.visible:
# extnode.parent_id = None
extnode.parent_namespace = None
extnode.parent = None
else:
extnode.parent_id = node.id
extnode.parent_namespace = node.namespace
extnode.parent = node
node.children.append(extnode)
removed = []
# find all not assigned nodes
for menu in self.renderer.menus.items():
if (hasattr(menu[1], 'cms_enabled')
and menu[1].cms_enabled and not menu[0] in exts):
for node in nodes:
if node.namespace == menu[0]:
removed.append(node)
if breadcrumb:
# if breadcrumb and home not in navigation add node
if breadcrumb and home and not home.visible:
home.visible = True
if request.path_info == home.get_absolute_url():
home.selected = True
else:
home.selected = False
# remove all nodes that are nav_extenders and not assigned
for node in removed:
nodes.remove(node)
return nodes
menu_pool.register_modifier(NavExtender)
class SoftRootCutter(Modifier):
"""
Ask evildmp/superdmp if you don't understand softroots!
Softroot description from the docs:
A soft root is a page that acts as the root for a menu navigation tree.
Typically, this will be a page that is the root of a significant new
section on your site.
When the soft root feature is enabled, the navigation menu for any page
will start at the nearest soft root, rather than at the real root of
the sites page hierarchy.
This feature is useful when your site has deep page hierarchies (and
therefore multiple levels in its navigation trees). In such a case, you
usually dont want to present site visitors with deep menus of nested
items.
For example, youre on the page -Introduction to Bleeding-?, so the menu
might look like this:
School of Medicine
Medical Education
Departments
Department of Lorem Ipsum
Department of Donec Imperdiet
Department of Cras Eros
Department of Mediaeval Surgery
Theory
Cures
Bleeding
Introduction to Bleeding <this is the current page>
Bleeding - the scientific evidence
Cleaning up the mess
Cupping
Leaches
Maggots
Techniques
Instruments
Department of Curabitur a Purus
Department of Sed Accumsan
Department of Etiam
Research
Administration
Contact us
Impressum
which is frankly overwhelming.
By making -Department of Mediaeval Surgery-? a soft root, the menu
becomes much more manageable:
Department of Mediaeval Surgery
Theory
Cures
Bleeding
Introduction to Bleeding <current page>
Bleeding - the scientific evidence
Cleaning up the mess
Cupping
Leaches
Maggots
Techniques
Instruments
"""
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
# only apply this modifier if we're pre-cut (since what we do is cut)
# or if no id argument is provided, indicating {% show_menu_below_id %}
if post_cut or root_id:
return nodes
selected = None
root_nodes = []
# find the selected node as well as all the root nodes
for node in nodes:
if node.selected:
selected = node
if not node.parent:
root_nodes.append(node)
# if we found a selected ...
if selected:
# and the selected is a softroot
if selected.attr.get("soft_root", False):
# get it's descendants
nodes = selected.get_descendants()
# remove the link to parent
selected.parent = None
# make the selected page the root in the menu
nodes = [selected] + nodes
else:
# if it's not a soft root, walk ancestors (upwards!)
nodes = self.find_ancestors_and_remove_children(selected, nodes)
return nodes
def find_and_remove_children(self, node, nodes):
for child in node.children:
if child.attr.get("soft_root", False):
self.remove_children(child, nodes)
return nodes
def remove_children(self, node, nodes):
for child in node.children:
nodes.remove(child)
self.remove_children(child, nodes)
node.children = []
def find_ancestors_and_remove_children(self, node, nodes):
"""
Check ancestors of node for soft roots
"""
if node.parent:
if node.parent.attr.get("soft_root", False):
nodes = node.parent.get_descendants()
node.parent.parent = None
nodes = [node.parent] + nodes
else:
nodes = self.find_ancestors_and_remove_children(
node.parent, nodes)
else:
for newnode in nodes:
if newnode != node and not newnode.parent:
self.find_and_remove_children(newnode, nodes)
for child in node.children:
if child != node:
self.find_and_remove_children(child, nodes)
return nodes
menu_pool.register_modifier(SoftRootCutter)
-137
View File
@@ -1,137 +0,0 @@
# -*- coding: utf-8 -*-
from cms.models import CMSPlugin, Placeholder
from cms.models.aliaspluginmodel import AliasPluginModel
from cms.models.placeholderpluginmodel import PlaceholderReference
from cms.plugin_base import CMSPluginBase, PluginMenuItem
from cms.plugin_pool import plugin_pool
from cms.utils.urlutils import admin_reverse
from django.conf.urls import url
from django.http import HttpResponseForbidden, HttpResponseBadRequest, HttpResponse
from django.middleware.csrf import get_token
from django.utils.translation import ugettext, ugettext_lazy as _, get_language
class PlaceholderPlugin(CMSPluginBase):
name = _("Placeholder")
parent_classes = ['0'] # so you will not be able to add it something
#require_parent = True
render_plugin = False
admin_preview = False
system = True
model = PlaceholderReference
plugin_pool.register_plugin(PlaceholderPlugin)
class AliasPlugin(CMSPluginBase):
name = _("Alias")
allow_children = False
model = AliasPluginModel
render_template = "cms/plugins/alias.html"
system = True
@classmethod
def get_render_queryset(cls):
queryset = super(AliasPlugin, cls).get_render_queryset()
return queryset.select_related('plugin', 'alias_placeholder')
@classmethod
def get_extra_plugin_menu_items(cls, request, plugin):
return [
PluginMenuItem(
_("Create Alias"),
admin_reverse("cms_create_alias"),
data={'plugin_id': plugin.pk, 'csrfmiddlewaretoken': get_token(request)},
)
]
@classmethod
def get_extra_placeholder_menu_items(cls, request, placeholder):
return [
PluginMenuItem(
_("Create Alias"),
admin_reverse("cms_create_alias"),
data={'placeholder_id': placeholder.pk, 'csrfmiddlewaretoken': get_token(request)},
)
]
def get_plugin_urls(self):
return [
url(r'^create_alias/$', self.create_alias, name='cms_create_alias'),
]
@classmethod
def get_empty_change_form_text(cls, obj=None):
original = super(AliasPlugin, cls).get_empty_change_form_text(obj=obj)
if not obj:
return original
instance = obj.get_plugin_instance()[0]
if not instance:
# Ghost plugin
return original
aliased_placeholder_id = instance.get_aliased_placeholder_id()
if not aliased_placeholder_id:
# Corrupt (sadly) Alias plugin
return original
aliased_placeholder = Placeholder.objects.get(pk=aliased_placeholder_id)
origin_page = aliased_placeholder.page
if not origin_page:
# Placeholder is not attached to a page
return original
# I have a feeling this could fail with a NoReverseMatch error
# if this is the case, then it's likely a corruption.
page_url = origin_page.get_absolute_url(language=obj.language)
page_title = origin_page.get_title(language=obj.language)
message = ugettext('This is an alias reference, '
'you can edit the content only on the '
'<a href="%(page_url)s?edit" target="_parent">%(page_title)s</a> page.')
return message % {'page_url': page_url, 'page_title': page_title}
def create_alias(self, request):
if not request.user.is_staff:
return HttpResponseForbidden("not enough privileges")
if not 'plugin_id' in request.POST and not 'placeholder_id' in request.POST:
return HttpResponseBadRequest("plugin_id or placeholder_id POST parameter missing.")
plugin = None
placeholder = None
if 'plugin_id' in request.POST:
pk = request.POST['plugin_id']
try:
plugin = CMSPlugin.objects.get(pk=pk)
except CMSPlugin.DoesNotExist:
return HttpResponseBadRequest("plugin with id %s not found." % pk)
if 'placeholder_id' in request.POST:
pk = request.POST['placeholder_id']
try:
placeholder = Placeholder.objects.get(pk=pk)
except Placeholder.DoesNotExist:
return HttpResponseBadRequest("placeholder with id %s not found." % pk)
if not placeholder.has_change_permission(request.user):
return HttpResponseBadRequest("You do not have enough permission to alias this placeholder.")
clipboard = request.toolbar.clipboard
clipboard.cmsplugin_set.all().delete()
language = get_language()
if plugin:
language = plugin.language
alias = AliasPluginModel(language=language, placeholder=clipboard, plugin_type="AliasPlugin")
if plugin:
alias.plugin = plugin
if placeholder:
alias.alias_placeholder = placeholder
alias.save()
return HttpResponse("ok")
plugin_pool.register_plugin(AliasPlugin)
-749
View File
@@ -1,749 +0,0 @@
# -*- coding: utf-8 -*-
from django.conf import settings
from django.contrib import admin
from django.contrib.auth import get_permission_codename, get_user_model
from django.contrib.auth.models import AnonymousUser
from django.contrib.sites.models import Site
from django.db.models import Q
from django.urls import NoReverseMatch, Resolver404, resolve, reverse
from django.utils.translation import override as force_language, ugettext_lazy as _
from cms.api import get_page_draft, can_change_page
from cms.constants import TEMPLATE_INHERITANCE_MAGIC, PUBLISHER_STATE_PENDING
from cms.models import Placeholder, Title, Page, PageType, StaticPlaceholder
from cms.toolbar.items import ButtonList, TemplateItem, REFRESH_PAGE
from cms.toolbar_base import CMSToolbar
from cms.toolbar_pool import toolbar_pool
from cms.utils import get_language_from_request, page_permissions
from cms.utils.conf import get_cms_setting
from cms.utils.i18n import get_language_tuple, get_language_dict
from cms.utils.page_permissions import (
user_can_change_page,
user_can_delete_page,
user_can_publish_page,
)
from cms.utils.urlutils import add_url_parameters, admin_reverse
from menus.utils import DefaultLanguageChanger
# Identifiers for search
ADMIN_MENU_IDENTIFIER = 'admin-menu'
LANGUAGE_MENU_IDENTIFIER = 'language-menu'
TEMPLATE_MENU_BREAK = 'Template Menu Break'
PAGE_MENU_IDENTIFIER = 'page'
PAGE_MENU_ADD_IDENTIFIER = 'add_page'
PAGE_MENU_FIRST_BREAK = 'Page Menu First Break'
PAGE_MENU_SECOND_BREAK = 'Page Menu Second Break'
PAGE_MENU_THIRD_BREAK = 'Page Menu Third Break'
PAGE_MENU_FOURTH_BREAK = 'Page Menu Fourth Break'
PAGE_MENU_LAST_BREAK = 'Page Menu Last Break'
HISTORY_MENU_BREAK = 'History Menu Break'
MANAGE_PAGES_BREAK = 'Manage Pages Break'
ADMIN_SITES_BREAK = 'Admin Sites Break'
ADMINISTRATION_BREAK = 'Administration Break'
CLIPBOARD_BREAK = 'Clipboard Break'
USER_SETTINGS_BREAK = 'User Settings Break'
ADD_PAGE_LANGUAGE_BREAK = "Add page language Break"
REMOVE_PAGE_LANGUAGE_BREAK = "Remove page language Break"
COPY_PAGE_LANGUAGE_BREAK = "Copy page language Break"
TOOLBAR_DISABLE_BREAK = 'Toolbar disable Break'
SHORTCUTS_BREAK = 'Shortcuts Break'
@toolbar_pool.register
class PlaceholderToolbar(CMSToolbar):
"""
Adds placeholder edit buttons if placeholders or static placeholders are detected in the template
"""
def populate(self):
self.page = get_page_draft(self.request.current_page)
def post_template_populate(self):
super(PlaceholderToolbar, self).post_template_populate()
self.add_wizard_button()
def add_wizard_button(self):
from cms.wizards.wizard_pool import entry_choices
title = _("Create")
if self.page:
user = self.request.user
page_pk = self.page.pk
disabled = len(list(entry_choices(user, self.page))) == 0
else:
page_pk = ''
disabled = True
url = '{url}?page={page}&language={lang}&edit'.format(
url=reverse("cms_wizard_create"),
page=page_pk,
lang=self.toolbar.site_language,
)
self.toolbar.add_modal_button(title, url,
side=self.toolbar.RIGHT,
disabled=disabled,
on_close=REFRESH_PAGE)
@toolbar_pool.register
class BasicToolbar(CMSToolbar):
"""
Basic Toolbar for site and languages menu
"""
page = None
_language_menu = None
_admin_menu = None
def init_from_request(self):
self.page = get_page_draft(self.request.current_page)
def populate(self):
if not self.page:
self.init_from_request()
self.clipboard = self.request.toolbar.user_settings.clipboard
self.add_admin_menu()
self.add_language_menu()
def add_admin_menu(self):
if not self._admin_menu:
self._admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER, self.current_site.name)
# Users button
self.add_users_button(self._admin_menu)
# sites menu
sites_queryset = Site.objects.order_by('name')
if len(sites_queryset) > 1:
sites_menu = self._admin_menu.get_or_create_menu('sites', _('Sites'))
sites_menu.add_sideframe_item(_('Admin Sites'), url=admin_reverse('sites_site_changelist'))
sites_menu.add_break(ADMIN_SITES_BREAK)
for site in sites_queryset:
sites_menu.add_link_item(site.name, url='http://%s' % site.domain,
active=site.pk == self.current_site.pk)
# admin
self._admin_menu.add_sideframe_item(_('Administration'), url=admin_reverse('index'))
self._admin_menu.add_break(ADMINISTRATION_BREAK)
# cms users settings
self._admin_menu.add_sideframe_item(_('User settings'), url=admin_reverse('cms_usersettings_change'))
self._admin_menu.add_break(USER_SETTINGS_BREAK)
# clipboard
if self.toolbar.edit_mode_active:
# True if the clipboard exists and there's plugins in it.
clipboard_is_bound = self.toolbar.clipboard_plugin
self._admin_menu.add_link_item(_('Clipboard...'), url='#',
extra_classes=['cms-clipboard-trigger'],
disabled=not clipboard_is_bound)
self._admin_menu.add_link_item(_('Clear clipboard'), url='#',
extra_classes=['cms-clipboard-empty'],
disabled=not clipboard_is_bound)
self._admin_menu.add_break(CLIPBOARD_BREAK)
# Disable toolbar
self._admin_menu.add_link_item(_('Disable toolbar'), url='?%s' % get_cms_setting('CMS_TOOLBAR_URL__DISABLE'))
self._admin_menu.add_break(TOOLBAR_DISABLE_BREAK)
self._admin_menu.add_link_item(_('Shortcuts...'), url='#',
extra_classes=('cms-show-shortcuts',))
self._admin_menu.add_break(SHORTCUTS_BREAK)
# logout
self.add_logout_button(self._admin_menu)
def add_users_button(self, parent):
User = get_user_model()
if User in admin.site._registry:
opts = User._meta
if self.request.user.has_perm('%s.%s' % (opts.app_label, get_permission_codename('change', opts))):
user_changelist_url = admin_reverse('%s_%s_changelist' % (opts.app_label, opts.model_name))
parent.add_sideframe_item(_('Users'), url=user_changelist_url)
def add_logout_button(self, parent):
# If current page is not published or has view restrictions user is redirected to the home page:
# * published page: no redirect
# * unpublished page: redirect to the home page
# * published page with login_required: redirect to the home page
# * published page with view permissions: redirect to the home page
page_is_published = self.page and self.page.is_published(self.current_lang)
if page_is_published and not self.page.login_required:
anon_can_access = page_permissions.user_can_view_page(
user=AnonymousUser(),
page=self.page,
site=self.current_site,
)
else:
anon_can_access = False
on_success = self.toolbar.REFRESH_PAGE if anon_can_access else '/'
# We'll show "Logout Joe Bloggs" if the name fields in auth.User are completed, else "Logout jbloggs". If
# anything goes wrong, it'll just be "Logout".
user_name = self.get_username()
logout_menu_text = _('Logout %s') % user_name if user_name else _('Logout')
parent.add_ajax_item(
logout_menu_text,
action=admin_reverse('logout'),
active=True,
on_success=on_success,
method='GET',
)
def add_language_menu(self):
if settings.USE_I18N and not self._language_menu:
self._language_menu = self.toolbar.get_or_create_menu(LANGUAGE_MENU_IDENTIFIER, _('Language'), position=-1)
language_changer = getattr(self.request, '_language_changer', DefaultLanguageChanger(self.request))
for code, name in get_language_tuple(self.current_site.pk):
try:
url = language_changer(code)
except NoReverseMatch:
url = DefaultLanguageChanger(self.request)(code)
self._language_menu.add_link_item(name, url=url, active=self.current_lang == code)
def get_username(self, user=None, default=''):
user = user or self.request.user
try:
name = user.get_full_name()
if name:
return name
else:
return user.get_username()
except (AttributeError, NotImplementedError):
return default
@toolbar_pool.register
class PageToolbar(CMSToolbar):
_changed_admin_menu = None
watch_models = [Page, PageType]
def init_placeholders(self):
request = self.request
toolbar = self.toolbar
if toolbar._async and 'placeholders[]' in request.GET:
# AJAX request to reload page structure
placeholder_ids = request.GET.getlist("placeholders[]")
self.placeholders = Placeholder.objects.filter(pk__in=placeholder_ids)
self.statics = StaticPlaceholder.objects.filter(
Q(draft__in=placeholder_ids) | Q(public__in=placeholder_ids)
)
self.dirty_statics = [sp for sp in self.statics if sp.dirty]
else:
if toolbar.structure_mode_active and not toolbar.uses_legacy_structure_mode:
# User has explicitly requested structure mode
# and the object (page, blog, etc..) allows for the non-legacy structure mode
renderer = toolbar.structure_renderer
else:
renderer = toolbar.get_content_renderer()
self.placeholders = renderer.get_rendered_placeholders()
self.statics = renderer.get_rendered_static_placeholders()
self.dirty_statics = [sp for sp in self.statics if sp.dirty]
def add_structure_mode(self):
if self.page and not self.page.application_urls:
if user_can_change_page(self.request.user, page=self.page):
return self.add_structure_mode_item()
elif any(ph for ph in self.placeholders if ph.has_change_permission(self.request.user)):
return self.add_structure_mode_item()
for sp in self.statics:
if sp.has_change_permission(self.request):
return self.add_structure_mode_item()
def add_structure_mode_item(self, extra_classes=('cms-toolbar-item-cms-mode-switcher',)):
structure_active = self.toolbar.structure_mode_active
edit_mode_active = (not structure_active and self.toolbar.edit_mode_active)
build_url = '{}?{}'.format(self.toolbar.request_path, get_cms_setting('CMS_TOOLBAR_URL__BUILD'))
edit_url = '{}?{}'.format(self.toolbar.request_path, get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON'))
if self.request.user.has_perm("cms.use_structure"):
switcher = self.toolbar.add_button_list('Mode Switcher', side=self.toolbar.RIGHT,
extra_classes=extra_classes)
switcher.add_button(_('Structure'), build_url, active=structure_active, disabled=False,
extra_classes='cms-structure-btn')
switcher.add_button(_('Content'), edit_url, active=edit_mode_active, disabled=False,
extra_classes='cms-content-btn')
def get_title(self):
try:
return Title.objects.get(page=self.page, language=self.current_lang, publisher_is_draft=True)
except Title.DoesNotExist:
return None
def has_publish_permission(self):
if self.page:
publish_permission = page_permissions.user_can_publish_page(
self.request.user,
page=self.page,
site=self.current_site
)
else:
publish_permission = False
if publish_permission and self.statics:
publish_permission = all(sp.has_publish_permission(self.request) for sp in self.dirty_statics)
return publish_permission
def has_unpublish_permission(self):
return self.has_publish_permission()
def has_page_change_permission(self):
if not hasattr(self, 'page_change_permission'):
self.page_change_permission = can_change_page(self.request)
return self.page_change_permission
def page_is_pending(self, page, language):
return (page.publisher_public_id and
page.publisher_public.get_publisher_state(language) == PUBLISHER_STATE_PENDING)
def in_apphook(self):
with force_language(self.toolbar.request_language):
try:
resolver = resolve(self.toolbar.request_path)
except Resolver404:
return False
else:
from cms.views import details
return resolver.func != details
def in_apphook_root(self):
"""
Returns True if the request is for a page handled by an apphook, but
is also the page it is attached to.
:return: Boolean
"""
page = getattr(self.request, 'current_page', False)
if page:
language = get_language_from_request(self.request)
return self.toolbar.request_path == page.get_absolute_url(language=language)
return False
def get_on_delete_redirect_url(self):
language = self.current_lang
parent_page = self.page.parent_page if self.page else None
# if the current page has a parent in the request's current language redirect to it
if parent_page and language in parent_page.get_languages():
with force_language(language):
return parent_page.get_absolute_url(language=language)
# else redirect to root, do not redirect to Page.objects.get_home() because user could have deleted the last
# page, if DEBUG == False this could cause a 404
return reverse('pages-root')
# Populate
def populate(self):
self.page = get_page_draft(self.request.current_page)
self.title = self.get_title()
self.permissions_activated = get_cms_setting('PERMISSION')
self.change_admin_menu()
self.add_page_menu()
self.change_language_menu()
def post_template_populate(self):
self.init_placeholders()
self.add_draft_live()
self.add_publish_button()
self.add_structure_mode()
def has_dirty_objects(self):
language = self.current_lang
if self.page:
if self.dirty_statics:
# There's dirty static placeholders on this page.
# Only show the page as dirty (publish button) if the page
# translation has been configured.
dirty = self.page.has_translation(language)
else:
dirty = (self.page.is_dirty(language) or self.page_is_pending(self.page, language))
else:
dirty = bool(self.dirty_statics)
return dirty
# Buttons
def add_publish_button(self, classes=('cms-btn-action', 'cms-btn-publish',)):
if self.user_can_publish():
button = self.get_publish_button(classes=classes)
self.toolbar.add_item(button)
def user_can_publish(self):
if self.page and self.page.is_page_type:
# By design, page-types are not publishable.
return False
if not self.toolbar.edit_mode_active:
return False
return self.has_publish_permission() and self.has_dirty_objects()
def get_publish_button(self, classes=None):
dirty = self.has_dirty_objects()
classes = list(classes or [])
if dirty and 'cms-btn-publish-active' not in classes:
classes.append('cms-btn-publish-active')
if self.dirty_statics or (self.page and self.page.is_published(self.current_lang)):
title = _('Publish page changes')
else:
title = _('Publish page now')
classes.append('cms-publish-page')
item = ButtonList(side=self.toolbar.RIGHT)
item.add_button(
title,
url=self.get_publish_url(),
disabled=not dirty,
extra_classes=classes,
)
return item
def get_publish_url(self):
pk = self.page.pk if self.page else 0
params = {}
if self.dirty_statics:
params['statics'] = ','.join(str(sp.pk) for sp in self.dirty_statics)
if self.in_apphook():
params['redirect'] = self.toolbar.request_path
with force_language(self.current_lang):
url = admin_reverse('cms_page_publish_page', args=(pk, self.current_lang))
return add_url_parameters(url, params)
def add_draft_live(self):
if self.page:
if self.toolbar.edit_mode_active and not self.title:
self.add_page_settings_button()
if user_can_change_page(self.request.user, page=self.page) and self.page.is_published(self.current_lang):
return self.add_draft_live_item()
elif self.placeholders:
return self.add_draft_live_item()
for sp in self.statics:
if sp.has_change_permission(self.request):
return self.add_draft_live_item()
def add_draft_live_item(self, template='cms/toolbar/items/live_draft.html', extra_context=None):
context = {'cms_toolbar': self.toolbar}
context.update(extra_context or {})
pos = len(self.toolbar.right_items)
self.toolbar.add_item(TemplateItem(template, extra_context=context, side=self.toolbar.RIGHT), position=pos)
def add_page_settings_button(self, extra_classes=('cms-btn-action',)):
url = '%s?language=%s' % (admin_reverse('cms_page_change', args=[self.page.pk]), self.toolbar.request_language)
self.toolbar.add_modal_button(_('Page settings'), url, side=self.toolbar.RIGHT, extra_classes=extra_classes)
# Menus
def change_language_menu(self):
if self.toolbar.edit_mode_active and self.page:
can_change = page_permissions.user_can_change_page(
user=self.request.user,
page=self.page,
site=self.current_site,
)
else:
can_change = False
if can_change:
language_menu = self.toolbar.get_menu(LANGUAGE_MENU_IDENTIFIER)
if not language_menu:
return None
languages = get_language_dict(self.current_site.pk)
remove = [(code, languages.get(code, code)) for code in self.page.get_languages() if code in languages]
add = [l for l in languages.items() if l not in remove]
copy = [(code, name) for code, name in languages.items() if code != self.current_lang and (code, name) in remove]
if add or remove or copy:
language_menu.add_break(ADD_PAGE_LANGUAGE_BREAK)
if add:
add_plugins_menu = language_menu.get_or_create_menu('{0}-add'.format(LANGUAGE_MENU_IDENTIFIER), _('Add Translation'))
if self.page.is_page_type:
page_change_url = admin_reverse('cms_pagetype_change', args=(self.page.pk,))
else:
page_change_url = admin_reverse('cms_page_change', args=(self.page.pk,))
for code, name in add:
url = add_url_parameters(page_change_url, language=code)
add_plugins_menu.add_modal_item(name, url=url)
if remove:
if self.page.is_page_type:
translation_delete_url = admin_reverse('cms_pagetype_delete_translation', args=(self.page.pk,))
else:
translation_delete_url = admin_reverse('cms_page_delete_translation', args=(self.page.pk,))
remove_plugins_menu = language_menu.get_or_create_menu('{0}-del'.format(LANGUAGE_MENU_IDENTIFIER), _('Delete Translation'))
disabled = len(remove) == 1
for code, name in remove:
url = add_url_parameters(translation_delete_url, language=code)
remove_plugins_menu.add_modal_item(name, url=url, disabled=disabled)
if copy:
copy_plugins_menu = language_menu.get_or_create_menu('{0}-copy'.format(LANGUAGE_MENU_IDENTIFIER), _('Copy all plugins'))
title = _('from %s')
question = _('Are you sure you want to copy all plugins from %s?')
if self.page.is_page_type:
page_copy_url = admin_reverse('cms_pagetype_copy_language', args=(self.page.pk,))
else:
page_copy_url = admin_reverse('cms_page_copy_language', args=(self.page.pk,))
for code, name in copy:
copy_plugins_menu.add_ajax_item(
title % name, action=page_copy_url,
data={'source_language': code, 'target_language': self.current_lang},
question=question % name, on_success=self.toolbar.REFRESH_PAGE
)
def change_admin_menu(self):
can_change_page = self.has_page_change_permission()
if not can_change_page:
# Check if the user has permissions to change at least one page
can_change_page = page_permissions.user_can_change_at_least_one_page(
user=self.request.user,
site=self.current_site,
)
if not self._changed_admin_menu and can_change_page:
admin_menu = self.toolbar.get_or_create_menu(ADMIN_MENU_IDENTIFIER)
url = admin_reverse('cms_page_changelist') # cms page admin
params = {'language': self.toolbar.request_language}
if self.page:
params['page_id'] = self.page.pk
url = add_url_parameters(url, params)
admin_menu.add_sideframe_item(_('Pages'), url=url, position=0)
# Used to prevent duplicates
self._changed_admin_menu = True
def add_page_menu(self):
if self.page:
edit_mode = self.toolbar.edit_mode_active
refresh = self.toolbar.REFRESH_PAGE
can_change = user_can_change_page(
user=self.request.user,
page=self.page,
site=self.current_site,
)
# menu for current page
# NOTE: disabled if the current path is "deeper" into the
# application's url patterns than its root. This is because
# when the Content Manager is at the root of the app-hook,
# some of the page options still make sense.
current_page_menu = self.toolbar.get_or_create_menu(
PAGE_MENU_IDENTIFIER, _('Page'), position=1, disabled=self.in_apphook() and not self.in_apphook_root())
new_page_params = {'edit': 1}
new_sub_page_params = {'edit': 1, 'parent_node': self.page.node_id}
if self.page.is_page_type:
add_page_url = admin_reverse('cms_pagetype_add')
advanced_url = admin_reverse('cms_pagetype_advanced', args=(self.page.pk,))
page_settings_url = admin_reverse('cms_pagetype_change', args=(self.page.pk,))
duplicate_page_url = admin_reverse('cms_pagetype_duplicate', args=[self.page.pk])
else:
add_page_url = admin_reverse('cms_page_add')
advanced_url = admin_reverse('cms_page_advanced', args=(self.page.pk,))
page_settings_url = admin_reverse('cms_page_change', args=(self.page.pk,))
duplicate_page_url = admin_reverse('cms_page_duplicate', args=[self.page.pk])
can_add_root_page = page_permissions.user_can_add_page(
user=self.request.user,
site=self.current_site,
)
if self.page.parent_page:
new_page_params['parent_node'] = self.page.parent_page.node_id
can_add_sibling_page = page_permissions.user_can_add_subpage(
user=self.request.user,
target=self.page.parent_page,
)
else:
can_add_sibling_page = can_add_root_page
can_add_sub_page = page_permissions.user_can_add_subpage(
user=self.request.user,
target=self.page,
)
# page operations menu
add_page_menu = current_page_menu.get_or_create_menu(
PAGE_MENU_ADD_IDENTIFIER,
_('Create Page'),
)
add_page_menu_modal_items = (
(_('New Page'), new_page_params, can_add_sibling_page),
(_('New Sub Page'), new_sub_page_params, can_add_sub_page),
)
for title, params, has_perm in add_page_menu_modal_items:
params.update(language=self.toolbar.request_language)
add_page_menu.add_modal_item(
title,
url=add_url_parameters(add_page_url, params),
disabled=not has_perm,
)
add_page_menu.add_modal_item(
_('Duplicate this Page'),
url=add_url_parameters(duplicate_page_url, {'language': self.toolbar.request_language}),
disabled=not can_add_sibling_page,
)
# first break
current_page_menu.add_break(PAGE_MENU_FIRST_BREAK)
# page edit
page_edit_url = '?%s' % get_cms_setting('CMS_TOOLBAR_URL__EDIT_ON')
current_page_menu.add_link_item(_('Edit this Page'), disabled=edit_mode, url=page_edit_url)
# page settings
page_settings_url = add_url_parameters(page_settings_url, language=self.toolbar.request_language)
settings_disabled = not edit_mode or not can_change
current_page_menu.add_modal_item(_('Page settings'), url=page_settings_url, disabled=settings_disabled,
on_close=refresh)
# advanced settings
advanced_url = add_url_parameters(advanced_url, language=self.toolbar.request_language)
can_change_advanced = self.page.has_advanced_settings_permission(self.request.user)
advanced_disabled = not edit_mode or not can_change_advanced
current_page_menu.add_modal_item(_('Advanced settings'), url=advanced_url, disabled=advanced_disabled)
# templates menu
if edit_mode:
if self.page.is_page_type:
action = admin_reverse('cms_pagetype_change_template', args=(self.page.pk,))
else:
action = admin_reverse('cms_page_change_template', args=(self.page.pk,))
if can_change_advanced:
templates_menu = current_page_menu.get_or_create_menu(
'templates',
_('Templates'),
disabled=not can_change,
)
for path, name in get_cms_setting('TEMPLATES'):
active = self.page.template == path
if path == TEMPLATE_INHERITANCE_MAGIC:
templates_menu.add_break(TEMPLATE_MENU_BREAK)
templates_menu.add_ajax_item(name, action=action, data={'template': path}, active=active,
on_success=refresh)
# page type
if not self.page.is_page_type:
page_type_url = admin_reverse('cms_pagetype_add')
page_type_url = add_url_parameters(page_type_url, source=self.page.pk, language=self.toolbar.request_language)
page_type_disabled = not edit_mode or not can_add_root_page
current_page_menu.add_modal_item(_('Save as Page Type'), page_type_url, disabled=page_type_disabled)
# second break
current_page_menu.add_break(PAGE_MENU_SECOND_BREAK)
# permissions
if self.permissions_activated:
permissions_url = admin_reverse('cms_page_permissions', args=(self.page.pk,))
permission_disabled = not edit_mode
if not permission_disabled:
permission_disabled = not page_permissions.user_can_change_page_permissions(
user=self.request.user,
page=self.page,
)
current_page_menu.add_modal_item(_('Permissions'), url=permissions_url, disabled=permission_disabled)
if not self.page.is_page_type:
# dates settings
dates_url = admin_reverse('cms_page_dates', args=(self.page.pk,))
current_page_menu.add_modal_item(
_('Publishing dates'),
url=dates_url,
disabled=(not edit_mode or not can_change),
)
# third break
current_page_menu.add_break(PAGE_MENU_THIRD_BREAK)
# navigation toggle
nav_title = _('Hide in navigation') if self.page.in_navigation else _('Display in navigation')
nav_action = admin_reverse('cms_page_change_innavigation', args=(self.page.pk,))
current_page_menu.add_ajax_item(
nav_title,
action=nav_action,
disabled=(not edit_mode or not can_change),
on_success=refresh,
)
# publisher
if self.title and not self.page.is_page_type:
if self.title.published:
publish_title = _('Unpublish page')
publish_url = admin_reverse('cms_page_unpublish', args=(self.page.pk, self.current_lang))
else:
publish_title = _('Publish page')
publish_url = admin_reverse('cms_page_publish_page', args=(self.page.pk, self.current_lang))
user_can_publish = user_can_publish_page(self.request.user, page=self.page)
current_page_menu.add_ajax_item(
publish_title,
action=publish_url,
disabled=not edit_mode or not user_can_publish,
on_success=refresh,
)
if self.current_lang and not self.page.is_page_type:
# revert to live
current_page_menu.add_break(PAGE_MENU_FOURTH_BREAK)
revert_action = admin_reverse('cms_page_revert_to_live', args=(self.page.pk, self.current_lang))
revert_question = _('Are you sure you want to revert to live?')
# Only show this action if the page has pending changes and a public version
is_enabled = (
edit_mode
and can_change
and self.page.is_dirty(self.current_lang)
and self.page.publisher_public
)
current_page_menu.add_ajax_item(
_('Revert to live'),
action=revert_action,
question=revert_question,
disabled=not is_enabled,
on_success=refresh,
extra_classes=('cms-toolbar-revert',),
)
# last break
current_page_menu.add_break(PAGE_MENU_LAST_BREAK)
# delete
if self.page.is_page_type:
delete_url = admin_reverse('cms_pagetype_delete', args=(self.page.pk,))
else:
delete_url = admin_reverse('cms_page_delete', args=(self.page.pk,))
delete_disabled = not edit_mode or not user_can_delete_page(self.request.user, page=self.page)
on_delete_redirect_url = self.get_on_delete_redirect_url()
current_page_menu.add_modal_item(_('Delete page'), url=delete_url, on_close=on_delete_redirect_url,
disabled=delete_disabled)
-57
View File
@@ -1,57 +0,0 @@
# -*- coding: utf-8 -*-
from django.utils.translation import ugettext_lazy as _
from cms.models import Page
from cms.utils.page_permissions import user_can_add_page, user_can_add_subpage
from .wizards.wizard_pool import wizard_pool
from .wizards.wizard_base import Wizard
from .forms.wizards import CreateCMSPageForm, CreateCMSSubPageForm
class CMSPageWizard(Wizard):
def user_has_add_permission(self, user, page=None, **kwargs):
if page:
parent_page = page.get_parent_page()
else:
parent_page = None
if page and page.get_parent_page():
# User is adding a page which will be a right
# sibling to the current page.
has_perm = user_can_add_subpage(user, target=parent_page)
else:
has_perm = user_can_add_page(user)
return has_perm
class CMSSubPageWizard(Wizard):
def user_has_add_permission(self, user, page=None, **kwargs):
if not page or page.application_urls:
# We can't really add a sub-page to a non-existent page. Or to an
# app-hooked page.
return False
return user_can_add_subpage(user, target=page)
cms_page_wizard = CMSPageWizard(
title=_(u"New page"),
weight=100,
form=CreateCMSPageForm,
model=Page,
description=_(u"Create a new page next to the current page.")
)
cms_subpage_wizard = CMSSubPageWizard(
title=_(u"New sub page"),
weight=110,
form=CreateCMSSubPageForm,
model=Page,
description=_(u"Create a page below the current page.")
)
wizard_pool.register(cms_page_wizard)
wizard_pool.register(cms_subpage_wizard)
-48
View File
@@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
TEMPLATE_INHERITANCE_MAGIC = 'INHERIT'
REFRESH_PAGE = 'REFRESH_PAGE'
FOLLOW_REDIRECT = 'FOLLOW_REDIRECT'
URL_CHANGE = 'URL_CHANGE'
RIGHT = object() # this is a trick so "foo is RIGHT" will only ever work for this, same goes for LEFT.
LEFT = object()
PUBLISHER_STATE_DEFAULT = 0
PUBLISHER_STATE_DIRTY = 1
# Page was marked published, but some of page parents are not.
PUBLISHER_STATE_PENDING = 4
PAGE_TYPES_ID = "page_types"
PAGE_TREE_POSITIONS = ('last-child', 'first-child', 'left', 'right')
VISIBILITY_ALL = None
VISIBILITY_USERS = 1
VISIBILITY_ANONYMOUS = 2
X_FRAME_OPTIONS_INHERIT = 0
X_FRAME_OPTIONS_DENY = 1
X_FRAME_OPTIONS_SAMEORIGIN = 2
X_FRAME_OPTIONS_ALLOW = 3
PAGE_USERNAME_MAX_LENGTH = 255
SLUG_REGEXP = '[0-9A-Za-z-_.//]+'
EXPIRE_NOW = 0
# HTTP Specification says max caching should only be up to one year.
MAX_EXPIRATION_TTL = 365 * 24 * 3600
PLUGIN_TOOLBAR_JS = "CMS._plugins.push([\"cms-plugin-%(pk)s\", %(config)s]);\n"
PLACEHOLDER_TOOLBAR_JS = "CMS._plugins.push([\"cms-placeholder-%(pk)s\", %(config)s]);"
# In the permissions system we use user levels to determine
# the depth in which the user has permissions.
# This constant represents a user that can see pages at all depths.
ROOT_USER_LEVEL = -1
GRANT_ALL_PERMISSIONS = 'All'
PUBLISH_COMMENT = "Publish"
SCRIPT_USERNAME = 'script'
-31
View File
@@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
from django.utils import lru_cache
from django.utils.functional import lazy
from cms.utils.conf import get_cms_setting
from cms.utils.page import get_page_template_from_request
def cms_settings(request):
"""
Adds cms-related variables to the context.
"""
from menus.menu_pool import MenuRenderer
@lru_cache.lru_cache(maxsize=None)
def _get_menu_renderer():
# We use lru_cache to avoid getting the manager
# every time this function is called.
from menus.menu_pool import menu_pool
return menu_pool.get_renderer(request)
# Now use lazy() to avoid getting the menu renderer
# up until the point is needed.
# lazy() does not memoize results, is why lru_cache is needed.
_get_menu_renderer = lazy(_get_menu_renderer, MenuRenderer)
return {
'cms_menu_renderer': _get_menu_renderer(),
'CMS_MEDIA_URL': get_cms_setting('MEDIA_URL'),
'CMS_TEMPLATE': lambda: get_page_template_from_request(request),
}
-84
View File
@@ -1,84 +0,0 @@
# -*- coding: utf-8 -*-
class PluginAlreadyRegistered(Exception):
pass
class PluginNotRegistered(Exception):
pass
class PluginLimitReached(Exception):
"""
Gets triggered when a placeholder has reached it's plugin limit.
"""
pass
class AppAlreadyRegistered(Exception):
pass
class ToolbarAlreadyRegistered(Exception):
pass
class ToolbarNotRegistered(Exception):
pass
class NotImplemented(Exception):
pass
class SubClassNeededError(Exception):
pass
class MissingFormError(Exception):
pass
class NoHomeFound(Exception):
pass
class PermissionsException(Exception):
"""Base permission exception
"""
class NoPermissionsException(PermissionsException):
"""Can be fired when some violate action is performed on permission system.
"""
class PublicIsUnmodifiable(Exception):
"""A method was invoked on the public copy, but is only valid for the
draft version"""
pass
class PublicVersionNeeded(Exception):
"""A Public version of this page is needed"""
pass
class Deprecated(Exception): pass
class DuplicatePlaceholderWarning(Warning): pass
class DontUsePageAttributeWarning(Warning): pass
class CMSDeprecationWarning(Warning): pass
class LanguageError(Exception): pass
class PluginConsistencyError(Exception): pass
class PlaceholderNotFound(Exception): pass
-5
View File
@@ -1,5 +0,0 @@
from .models import PageExtension # nopyflakes
from .models import TitleExtension # nopyflakes
from .extension_pool import extension_pool # nopyflakes
from .admin import PageExtensionAdmin # nopyflakes
from .admin import TitleExtensionAdmin # nopyflakes
-109
View File
@@ -1,109 +0,0 @@
from cms.models import Page, Title
from cms.utils.page_permissions import user_can_change_page
from django.contrib import admin
from django.contrib.admin.options import csrf_protect_m
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.urls import reverse
class ExtensionAdmin(admin.ModelAdmin):
change_form_template = "admin/cms/extensions/change_form.html"
add_form_template = "admin/cms/extensions/change_form.html"
class PageExtensionAdmin(ExtensionAdmin):
def save_model(self, request, obj, form, change):
if not change and 'extended_object' in request.GET:
obj.extended_object = Page.objects.get(pk=request.GET['extended_object'])
page = Page.objects.get(pk=request.GET['extended_object'])
else:
page = obj.extended_object
if not user_can_change_page(request.user, page):
raise PermissionDenied()
super(PageExtensionAdmin, self).save_model(request, obj, form, change)
def delete_model(self, request, obj):
if not obj.extended_object.has_change_permission(request.user):
raise PermissionDenied()
obj.delete()
def get_model_perms(self, request):
"""
Return empty perms dict thus hiding the model from admin index.
"""
return {}
def get_queryset(self, request):
return super(PageExtensionAdmin, self).get_queryset(request).filter(extended_object__publisher_is_draft=True)
@csrf_protect_m
def add_view(self, request, form_url='', extra_context=None):
"""
Check if the page already has an extension object. If so, redirect to edit view instead.
"""
extended_object_id = request.GET.get('extended_object', False)
if extended_object_id:
try:
page = Page.objects.get(pk=extended_object_id)
extension = self.model.objects.get(extended_object=page)
opts = self.model._meta
change_url = reverse('admin:%s_%s_change' %
(opts.app_label, opts.model_name),
args=(extension.pk,),
current_app=self.admin_site.name)
return HttpResponseRedirect(change_url)
except self.model.DoesNotExist:
pass
return super(ExtensionAdmin, self).add_view(request, form_url, extra_context)
class TitleExtensionAdmin(ExtensionAdmin):
def save_model(self, request, obj, form, change):
if not change and 'extended_object' in request.GET:
obj.extended_object = Title.objects.get(pk=request.GET['extended_object'])
title = Title.objects.get(pk=request.GET['extended_object'])
else:
title = obj.extended_object
if not user_can_change_page(request.user, page=title.page):
raise PermissionDenied()
super(TitleExtensionAdmin, self).save_model(request, obj, form, change)
def delete_model(self, request, obj):
page = obj.extended_object.page
if not user_can_change_page(request.user, page):
raise PermissionDenied()
obj.delete()
def get_model_perms(self, request):
"""
Return empty perms dict thus hiding the model from admin index.
"""
return {}
def get_queryset(self, request):
return super(TitleExtensionAdmin, self).get_queryset(request).filter(extended_object__page__publisher_is_draft=True)
@csrf_protect_m
def add_view(self, request, form_url='', extra_context=None):
"""
Check if the page already has an extension object. If so, redirect to edit view instead.
"""
extended_object_id = request.GET.get('extended_object', False)
if extended_object_id:
try:
title = Title.objects.get(pk=extended_object_id)
extension = self.model.objects.get(extended_object=title)
opts = self.model._meta
change_url = reverse('admin:%s_%s_change' %
(opts.app_label, opts.model_name),
args=(extension.pk,),
current_app=self.admin_site.name)
return HttpResponseRedirect(change_url)
except self.model.DoesNotExist:
pass
return super(ExtensionAdmin, self).add_view(request, form_url, extra_context)
-148
View File
@@ -1,148 +0,0 @@
from cms.exceptions import SubClassNeededError
from .models import PageExtension, TitleExtension
class ExtensionPool(object):
def __init__(self):
self.page_extensions = set()
self.title_extensions = set()
self.signaling_activated = False
def register(self, extension):
"""
Registers the given extension.
Example::
class MyExtension(PageExtension):
pass
extension_pool.register(MyExtension)
or as decorator::
@extension_pool.register
class MyExtension(PageExtension):
pass
"""
if issubclass(extension, PageExtension):
self.page_extensions.add(extension)
elif issubclass(extension, TitleExtension):
self.title_extensions.add(extension)
else:
raise SubClassNeededError(
'Extension has to subclass either %r or %r. %r does not!' % (PageExtension, TitleExtension, extension)
)
self._activate_signaling()
return extension
def unregister(self, extension):
"""
Unregisters the given extension. No error is thrown if given extension isn't an extension or wasn't
registered yet.
"""
try:
if issubclass(extension, PageExtension):
self.page_extensions.remove(extension)
elif issubclass(extension, TitleExtension):
self.title_extensions.remove(extension)
except KeyError:
pass
def _activate_signaling(self):
"""
Activates the post_publish signal receiver if not already done.
"""
if not self.signaling_activated:
from cms.signals import post_publish
post_publish.connect(self._receiver)
self.signaling_activated = True
def _receiver(self, sender, **kwargs):
"""
Receiver for the post_publish signal. Gets the published page from kwargs.
"""
# instance from kwargs is the draft page
draft_page = kwargs.get('instance')
language = kwargs.get('language')
# get the new public page from the draft page
public_page = draft_page.publisher_public
if self.page_extensions:
self._copy_page_extensions(draft_page, public_page, language, clone=False)
self._remove_orphaned_page_extensions()
if self.title_extensions:
self._copy_title_extensions(draft_page, None, language, clone=False)
self._remove_orphaned_title_extensions()
def _copy_page_extensions(self, source_page, target_page, language, clone=False):
for extension in self.page_extensions:
for instance in extension.objects.filter(extended_object=source_page):
if clone:
instance.copy(target_page, language)
else:
instance.copy_to_public(target_page, language)
def _copy_title_extensions(self, source_page, target_page, language, clone=False):
source_title = source_page.title_set.get(language=language)
if target_page:
target_title = target_page.title_set.get(language=language)
else:
target_title = source_title.publisher_public
for extension in self.title_extensions:
for instance in extension.objects.filter(extended_object=source_title):
if clone:
instance.copy(target_title, language)
else:
instance.copy_to_public(target_title, language)
def copy_extensions(self, source_page, target_page, languages=None):
if not languages:
languages = target_page.get_languages()
if self.page_extensions:
self._copy_page_extensions(source_page, target_page, None, clone=True)
self._remove_orphaned_page_extensions()
for language in languages:
if self.title_extensions:
self._copy_title_extensions(source_page, target_page, language, clone=True)
self._remove_orphaned_title_extensions()
def _remove_orphaned_page_extensions(self):
for extension in self.page_extensions:
extension.objects.filter(
extended_object__publisher_is_draft=False,
draft_extension=None
).delete()
def _remove_orphaned_title_extensions(self):
for extension in self.title_extensions:
extension.objects.filter(
extended_object__page__publisher_is_draft=False,
draft_extension=None
).delete()
def get_page_extensions(self, page=None):
extensions = []
for extension in self.page_extensions:
if page:
extensions.extend(list(extension.objects.filter(extended_object=page)))
else:
extensions.extend(list(extension.objects.all()))
return extensions
def get_title_extensions(self, title=None):
extensions = []
for extension in self.title_extensions:
if title:
extensions.extend(list(extension.objects.filter(extended_object=title)))
else:
extensions.extend(list(extension.objects.all()))
return extensions
extension_pool = ExtensionPool()
-152
View File
@@ -1,152 +0,0 @@
from django.db.models import ManyToManyField
from cms.constants import PUBLISHER_STATE_DIRTY
from django.db import models
from cms.models import Page, Title
class BaseExtension(models.Model):
public_extension = models.OneToOneField(
'self',
on_delete=models.CASCADE,
null=True,
editable=False,
related_name='draft_extension',
)
extended_object = None
class Meta:
abstract = True
def get_page(self): # pragma: no cover
raise NotImplementedError('Function must be overwritten in subclasses and return the extended page object.')
def copy_relations(self, oldinstance, language):
"""
Copy relations like many to many or foreign key relations to the public version.
Similar to the same named cms plugin function.
:param oldinstance: the draft version of the extension
"""
pass
@classmethod
def _get_related_objects(cls):
fields = cls._meta._get_fields(
forward=False, reverse=True,
include_parents=True,
include_hidden=False,
)
return list(obj for obj in fields if not isinstance(obj.field, ManyToManyField))
def copy(self, target, language):
"""
This method copies this extension to an unrelated-target. If you intend
to "publish" this extension to the publisher counterpart of target, then
use copy_to_publish() instead.
"""
clone = self.__class__.objects.get(pk=self.pk) # get a copy of this instance
clone.pk = None
clone.public_extension = None
clone.extended_object = target # set the new public object
# Nullify all concrete parent primary keys. See issue #5494
for parent, field in clone._meta.parents.items():
if field:
setattr(clone, parent._meta.pk.attname, None)
clone.save(mark_page=False)
# If the target we're copying already has a publisher counterpart, then
# connect the dots.
target_prime = getattr(target, 'publisher_public')
if target_prime:
related_name = self.__class__.__name__.lower()
clone_prime = getattr(target_prime, related_name)
if clone_prime:
clone.public_extension = clone_prime
else:
clone.public_extension = None
clone.copy_relations(self, language)
clone.save(force_update=True, mark_page=False)
return clone
def copy_to_public(self, public_object, language):
"""
This method is used to "publish" this extension as part of the a larger
operation on the target. If you intend to copy this extension to an
unrelated object, use copy() instead.
"""
this = self.__class__.objects.get(pk=self.pk) # get a copy of this instance
public_extension = self.public_extension # get the public version of this instance if any
this.extended_object = public_object # set the new public object
if public_extension:
this.pk = public_extension.pk # overwrite current public extension
this.public_extension = None # remove public extension or it will point to itself and raise duplicate entry
# Set public_extension concrete parents PKs. See issue #5494
for parent, field in this._meta.parents.items():
if field:
setattr(this, parent._meta.pk.attname, getattr(public_extension, parent._meta.pk.attname))
else:
this.pk = None # create new public extension
# Nullify all concrete parent primary keys. See issue #5494
for parent, field in this._meta.parents.items():
if field:
setattr(this, parent._meta.pk.attname, None)
this.save(mark_page=False)
self.public_extension = this
self.save(mark_page=False)
this.copy_relations(self, language)
this.save(force_update=True, mark_page=False)
return this
class PageExtension(BaseExtension):
extended_object = models.OneToOneField(Page, on_delete=models.CASCADE, editable=False)
class Meta:
abstract = True
def get_page(self):
return self.extended_object
def save(self, *args, **kwargs):
if kwargs.pop('mark_page', True):
self.get_page().title_set.update(publisher_state=PUBLISHER_STATE_DIRTY) # mark page dirty
return super(BaseExtension, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
if kwargs.pop('mark_page', True):
self.get_page().title_set.update(publisher_state=PUBLISHER_STATE_DIRTY) # mark page dirty
return super(BaseExtension, self).delete(*args, **kwargs)
class TitleExtension(BaseExtension):
extended_object = models.OneToOneField(Title, on_delete=models.CASCADE, editable=False)
class Meta:
abstract = True
def get_page(self):
return self.extended_object.page
def save(self, *args, **kwargs):
if kwargs.pop('mark_page', True):
Title.objects.filter(pk=self.extended_object.pk).update(
publisher_state=PUBLISHER_STATE_DIRTY) # mark title dirty
return super(BaseExtension, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
if kwargs.pop('mark_page', True):
Title.objects.filter(pk=self.extended_object.pk).update(
publisher_state=PUBLISHER_STATE_DIRTY) # mark title dirty
return super(BaseExtension, self).delete(*args, **kwargs)
-110
View File
@@ -1,110 +0,0 @@
# -*- coding: utf-8 -*-
from cms.utils.urlutils import admin_reverse
from cms.api import get_page_draft
from cms.toolbar_base import CMSToolbar
from cms.utils import get_language_list
from cms.utils.page_permissions import user_can_change_page
from django.urls import NoReverseMatch
class ExtensionToolbar(CMSToolbar):
model = None
page = None
def _setup_extension_toolbar(self):
"""
Does all the sanity check for the current environment:
* that a page exists
* permissions check on the current page
It returns the page menu or None if the above conditions are not met
"""
page = self._get_page()
if page and user_can_change_page(self.request.user, page=page):
return self.toolbar.get_or_create_menu('page')
return
def _get_page(self):
"""
A utility method that caches the current page and make sure to use the draft version of the page.
"""
# always use draft if we have a page
if not self.page:
self.page = get_page_draft(self.request.current_page)
return self.page
def get_page_extension_admin(self):
"""
Get the admin url for the page extension menu item, depending on whether a PageExtension instance exists
for the current page or not.
Return a tuple of the current extension and the url; the extension is None if no instance exists,
the url is None is no admin is registered for the extension.
"""
page = self._get_page()
# Page extension
try:
page_extension = self.model.objects.get(extended_object_id=page.pk)
except self.model.DoesNotExist:
page_extension = None
try:
model_name = self.model.__name__.lower()
if page_extension:
admin_url = admin_reverse(
'%s_%s_change' % (self.model._meta.app_label, model_name),
args=(page_extension.pk,))
else:
admin_url = "%s?extended_object=%s" % (
admin_reverse('%s_%s_add' % (self.model._meta.app_label, model_name)),
self.page.pk)
except NoReverseMatch: # pragma: no cover
admin_url = None
return page_extension, admin_url
def get_title_extension_admin(self, language=None):
"""
Get the admin urls for the title extensions menu items, depending on whether a TitleExtension instance exists
for each Title in the current page.
A single language can be passed to only work on a single title.
Return a list of tuples of the title extension and the url; the extension is None if no instance exists,
the url is None is no admin is registered for the extension.
"""
page = self._get_page()
urls = []
if language:
titles = page.get_title_obj(language),
else:
titles = page.title_set.filter(language__in=get_language_list(page.node.site_id))
# Titles
for title in titles:
try:
title_extension = self.model.objects.get(extended_object_id=title.pk)
except self.model.DoesNotExist:
title_extension = None
try:
model_name = self.model.__name__.lower()
if title_extension:
admin_url = admin_reverse(
'%s_%s_change' % (self.model._meta.app_label, model_name),
args=(title_extension.pk,))
else:
admin_url = "%s?extended_object=%s" % (
admin_reverse('%s_%s_add' % (self.model._meta.app_label, model_name)),
title.pk)
except NoReverseMatch: # pragma: no cover
admin_url = None
if admin_url:
urls.append((title_extension, admin_url))
return urls
def _get_sub_menu(self, current_menu, key, label, position=None):
"""
Utility function to get a submenu of the current menu
"""
extension_menu = current_menu.get_or_create_menu(
key, label, position=position)
return extension_menu
View File
-98
View File
@@ -1,98 +0,0 @@
# -*- coding: utf-8 -*-
from django import forms
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
from django.forms.fields import EMPTY_VALUES
from django.utils.translation import ugettext_lazy as _
from cms.forms.utils import get_site_choices, get_page_choices
from cms.forms.validators import validate_url
from cms.forms.widgets import PageSelectWidget, PageSmartLinkWidget
from cms.models.pagemodel import Page
class SuperLazyIterator(object):
def __init__(self, func):
self.func = func
def __iter__(self):
return iter(self.func())
class LazyChoiceField(forms.ChoiceField):
def _set_choices(self, value):
# we overwrite this function so no list(value) is called
self._choices = self.widget.choices = value
choices = property(forms.ChoiceField._get_choices, _set_choices)
class PageSelectFormField(forms.MultiValueField):
widget = PageSelectWidget
default_error_messages = {
'invalid_site': _(u'Select a valid site'),
'invalid_page': _(u'Select a valid page'),
}
def __init__(self, queryset=None, empty_label=u"---------", cache_choices=False,
required=True, widget=None, to_field_name=None, limit_choices_to=None,
*args, **kwargs):
errors = self.default_error_messages.copy()
if 'error_messages' in kwargs:
errors.update(kwargs['error_messages'])
site_choices = SuperLazyIterator(get_site_choices)
page_choices = SuperLazyIterator(get_page_choices)
self.limit_choices_to = limit_choices_to
kwargs['required'] = required
fields = (
LazyChoiceField(choices=site_choices, required=False, error_messages={'invalid': errors['invalid_site']}),
LazyChoiceField(choices=page_choices, required=False, error_messages={'invalid': errors['invalid_page']}),
)
super(PageSelectFormField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
if data_list:
page_id = data_list[1]
if page_id in EMPTY_VALUES:
if not self.required:
return None
raise forms.ValidationError(self.error_messages['invalid_page'])
return Page.objects.get(pk=page_id)
return None
def has_changed(self, initial, data):
is_empty = data and (len(data) >= 2 and data[1] in [None, ''])
if isinstance(self.widget, RelatedFieldWidgetWrapper):
self.widget.decompress = self.widget.widget.decompress
if is_empty and initial is None:
# when empty data will have [u'1', u'', u''] as value
# this will cause django to always return True because of the '1'
# so we simply follow django's default behavior when initial is None and data is "empty"
data = ['' for x in range(0, len(data))]
return super(PageSelectFormField, self).has_changed(initial, data)
def _has_changed(self, initial, data):
return self.has_changed(initial, data)
class PageSmartLinkField(forms.CharField):
widget = PageSmartLinkWidget
default_validators = [validate_url]
def __init__(self, max_length=None, min_length=None, placeholder_text=None,
ajax_view=None, *args, **kwargs):
self.placeholder_text = placeholder_text
widget = self.widget(ajax_view=ajax_view)
super(PageSmartLinkField, self).__init__(max_length=max_length, min_length=min_length,
widget=widget, *args, **kwargs)
def widget_attrs(self, widget):
attrs = super(PageSmartLinkField, self).widget_attrs(widget)
attrs.update({'placeholder_text': self.placeholder_text})
return attrs
def clean(self, value):
value = self.to_python(value).strip()
return super(PageSmartLinkField, self).clean(value)
-13
View File
@@ -1,13 +0,0 @@
from django import forms
from django.contrib.auth.forms import AuthenticationForm
class CMSToolbarLoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(CMSToolbarLoginForm, self).__init__(*args, **kwargs)
kwargs['prefix'] = kwargs.get('prefix', 'cms')
self.fields['username'].widget = forms.TextInput(
attrs = { 'required': 'required' })
self.fields['password'].widget = forms.PasswordInput(
attrs = { 'required': 'required' })
-104
View File
@@ -1,104 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db.models import Prefetch
from django.contrib.sites.models import Site
from django.db.models.signals import post_save, post_delete
from django.utils.html import escape
from django.utils.safestring import mark_safe
from cms.cache.choices import (
clean_site_choices_cache, clean_page_choices_cache,
_site_cache_key, _page_cache_key)
from cms.models import Page, Title
from cms.utils import i18n
def get_sites():
sites = (
Site
.objects
.filter(djangocms_nodes__isnull=False)
.order_by('name')
.distinct()
)
return sites
def get_page_choices_for_site(site, language):
fallbacks = i18n.get_fallback_languages(language, site_id=site.pk)
languages = [language] + fallbacks
translation_lookup = Prefetch(
'title_set',
to_attr='filtered_translations',
queryset=Title.objects.filter(language__in=languages).only('pk', 'page', 'language', 'title')
)
pages = (
Page
.objects
.drafts()
.on_site(site)
.select_related('node')
.prefetch_related(translation_lookup)
.order_by('node__path')
.only('pk', 'node')
)
for page in pages:
translations = page.filtered_translations
titles_by_language = {trans.language: trans.title for trans in translations}
for language in languages:
# EmptyTitle is used to prevent the cms from trying
# to find a translation in the database
if language in titles_by_language:
title = titles_by_language[language]
indent = "&nbsp;&nbsp;" * (page.node.depth - 1)
label = mark_safe("%s%s" % (indent, escape(title)))
yield (page.pk, label)
break
def update_site_and_page_choices(language=None):
if language is None:
language = i18n.get_current_language()
site_choices = []
page_choices = [('', '----')]
site_choices_key = _site_cache_key(language)
page_choices_key = _page_cache_key(language)
for site in get_sites():
_page_choices = list(get_page_choices_for_site(site, language))
site_choices.append((site.pk, site.name))
page_choices.append((site.name, _page_choices))
from django.core.cache import cache
# We set it to 1 day here because we actively invalidate this cache.
cache.set(site_choices_key, site_choices, 86400)
cache.set(page_choices_key, page_choices, 86400)
return site_choices, page_choices
def get_site_choices(lang=None):
from django.core.cache import cache
lang = lang or i18n.get_current_language()
site_choices = cache.get(_site_cache_key(lang))
if site_choices is None:
site_choices = update_site_and_page_choices(lang)[0]
return site_choices
def get_page_choices(lang=None):
from django.core.cache import cache
lang = lang or i18n.get_current_language()
page_choices = cache.get(_page_cache_key(lang))
if page_choices is None:
page_choices = update_site_and_page_choices(lang)[1]
return page_choices
post_save.connect(clean_page_choices_cache, sender=Page)
post_save.connect(clean_site_choices_cache, sender=Site)
post_delete.connect(clean_page_choices_cache, sender=Page)
post_delete.connect(clean_site_choices_cache, sender=Site)
-70
View File
@@ -1,70 +0,0 @@
from __future__ import unicode_literals
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator, URLValidator
from django.utils.encoding import force_text
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext
from cms.utils.page import get_all_pages_from_path
from cms.utils.urlutils import admin_reverse, relative_url_regex
def validate_relative_url(value):
RegexValidator(regex=relative_url_regex)(value)
def validate_url(value):
try:
# Validate relative urls first
validate_relative_url(value)
except ValidationError:
# Fallback to absolute urls
URLValidator()(value)
def validate_url_uniqueness(site, path, language, exclude_page=None):
""" Checks for conflicting urls
"""
if '/' in path:
validate_url(path)
path = path.strip('/')
pages = get_all_pages_from_path(site, path, language)
pages = pages.select_related('publisher_public')
if exclude_page:
pages = pages.exclude(pk=exclude_page.pk)
if exclude_page.publisher_public_id:
pages = pages.exclude(pk=exclude_page.publisher_public_id)
try:
conflict_page = pages[0]
except IndexError:
return True
if conflict_page.publisher_is_draft:
page_id = conflict_page.pk
else:
# rare case where draft points to one url
# and live points to another which conflicts.
# Use the draft ID because public page is not editable.
page_id = conflict_page.publisher_public_id
if conflict_page.is_page_type:
change_url = admin_reverse('cms_pagetype_change', args=[page_id])
else:
change_url = admin_reverse('cms_page_change', args=[page_id])
conflict_url = '<a href="%(change_url)s" target="_blank">%(page_title)s</a>' % {
'change_url': change_url,
'page_title': force_text(conflict_page),
}
if exclude_page:
message = ugettext('Page %(conflict_page)s has the same url \'%(url)s\' as current page "%(instance)s".')
else:
message = ugettext('Page %(conflict_page)s has the same url \'%(url)s\' as current page.')
message = message % {'conflict_page': conflict_url, 'url': path, 'instance': exclude_page}
raise ValidationError(mark_safe(message))
-262
View File
@@ -1,262 +0,0 @@
# -*- coding: utf-8 -*-
from django.contrib.auth import get_permission_codename
from django.contrib.sites.models import Site
from django.forms.widgets import MultiWidget, Select, TextInput
from django.urls import NoReverseMatch, reverse_lazy
from django.utils.encoding import force_text
from django.utils.html import escape, escapejs
from django.utils.safestring import mark_safe
from cms.utils.urlutils import admin_reverse, static_with_version
from cms.forms.utils import get_site_choices, get_page_choices
from cms.models import Page, PageUser
class PageSelectWidget(MultiWidget):
"""A widget that allows selecting a page by first selecting a site and then
a page on that site in a two step process.
"""
template_name = 'cms/widgets/pageselectwidget.html'
class Media:
js = (
static_with_version('cms/js/dist/bundle.forms.pageselectwidget.min.js'),
)
def __init__(self, site_choices=None, page_choices=None, attrs=None):
if attrs is not None:
self.attrs = attrs.copy()
else:
self.attrs = {}
self.choices = []
super(PageSelectWidget, self).__init__((Select, Select, Select), attrs)
def decompress(self, value):
"""
receives a page_id in value and returns the site_id and page_id
of that page or the current site_id and None if no page_id is given.
"""
if value:
page = Page.objects.select_related('node').get(pk=value)
return [page.node.site_id, page.pk, page.pk]
site = Site.objects.get_current()
return [site.pk,None,None]
def _has_changed(self, initial, data):
# THIS IS A COPY OF django.forms.widgets.Widget._has_changed()
# (except for the first if statement)
"""
Return True if data differs from initial.
"""
# For purposes of seeing whether something has changed, None is
# the same as an empty string, if the data or inital value we get
# is None, replace it w/ u''.
if data is None or (len(data)>=2 and data[1] in [None,'']):
data_value = u''
else:
data_value = data
if initial is None:
initial_value = u''
else:
initial_value = initial
if force_text(initial_value) != force_text(data_value):
return True
return False
def _build_widgets(self):
site_choices = get_site_choices()
page_choices = get_page_choices()
self.site_choices = site_choices
self.choices = page_choices
self.widgets = (Select(choices=site_choices ),
Select(choices=[('', '----')]),
Select(choices=self.choices, attrs={'style': "display:none;"} ),
)
def _build_script(self, name, value, attrs={}):
return r"""<script type="text/javascript">
var CMS = window.CMS || {};
CMS.Widgets = CMS.Widgets || {};
CMS.Widgets._pageSelectWidgets = CMS.Widgets._pageSelectWidgets || [];
CMS.Widgets._pageSelectWidgets.push({
name: '%(name)s'
});
</script>""" % {
'name': name
}
def get_context(self, name, value, attrs):
self._build_widgets()
context = super(PageSelectWidget, self).get_context(name, value, attrs)
context['widget']['script_init'] = self._build_script(name, value, context['widget']['attrs'])
return context
def format_output(self, rendered_widgets):
return u' '.join(rendered_widgets)
class PageSmartLinkWidget(TextInput):
template_name = 'cms/widgets/pagesmartlinkwidget.html'
class Media:
css = {
'all': (
'cms/js/select2/select2.css',
'cms/js/select2/select2-bootstrap.css',
)
}
js = (
static_with_version('cms/js/dist/bundle.forms.pagesmartlinkwidget.min.js'),
)
def __init__(self, attrs=None, ajax_view=None):
super(PageSmartLinkWidget, self).__init__(attrs)
self.ajax_url = self.get_ajax_url(ajax_view=ajax_view)
def get_ajax_url(self, ajax_view):
try:
return reverse_lazy(ajax_view)
except NoReverseMatch:
raise Exception(
'You should provide an ajax_view argument that can be reversed to the PageSmartLinkWidget'
)
def _build_script(self, name, value, attrs={}):
return r"""<script type="text/javascript">
var CMS = window.CMS || {};
CMS.Widgets = CMS.Widgets || {};
CMS.Widgets._pageSmartLinkWidgets = CMS.Widgets._pageSmartLinkWidgets || [];
CMS.Widgets._pageSmartLinkWidgets.push({
id: '%(element_id)s',
text: '%(placeholder_text)s',
lang: '%(language_code)s',
url: '%(ajax_url)s'
});
</script>""" % {
'element_id': attrs.get('id', ''),
'placeholder_text': attrs.get('placeholder_text', ''),
'language_code': self.language,
'ajax_url': force_text(self.ajax_url)
}
def get_context(self, name, value, attrs):
context = super(PageSmartLinkWidget, self).get_context(name, value, attrs)
context['widget']['script_init'] = self._build_script(name, value, context['widget']['attrs'])
return context
class UserSelectAdminWidget(Select):
"""Special widget used in page permission inlines, because we have to render
an add user (plus) icon, but point it somewhere else - to special user creation
view, which is accessible only if user haves "add user" permissions.
Current user should be assigned to widget in form constructor as an user
attribute.
"""
def render(self, name, value, attrs=None, choices=(), renderer=None):
output = [super(UserSelectAdminWidget, self).render(name, value, attrs, renderer=renderer)]
if hasattr(self, 'user') and (self.user.is_superuser or \
self.user.has_perm(PageUser._meta.app_label + '.' + get_permission_codename('add', PageUser._meta))):
# append + icon
add_url = admin_reverse('cms_pageuser_add')
output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
(add_url, name))
return mark_safe(u''.join(output))
class AppHookSelect(Select):
"""Special widget used for the App Hook selector in the Advanced Settings
of the Page Admin. It adds support for a data attribute per option and
includes supporting JS into the page.
"""
class Media:
js = (
static_with_version('cms/js/dist/bundle.forms.apphookselect.min.js'),
)
def __init__(self, attrs=None, choices=(), app_namespaces={}):
self.app_namespaces = app_namespaces
super(AppHookSelect, self).__init__(attrs, choices)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super(AppHookSelect, self).create_option(name, value, label, selected, index, subindex, attrs)
if value in self.app_namespaces:
option['attrs']['data-namespace'] = escape(self.app_namespaces[value])
return option
def _build_option(self, selected_choices, option_value, option_label):
if option_value is None:
option_value = ''
option_value = force_text(option_value)
if option_value in selected_choices:
selected_html = mark_safe(' selected="selected"')
if not self.allow_multiple_selected:
# Only allow for a single selection.
selected_choices.remove(option_value)
else:
selected_html = ''
if option_value in self.app_namespaces:
data_html = mark_safe(' data-namespace="%s"' % escape(self.app_namespaces[option_value]))
else:
data_html = ''
return option_value, selected_html, data_html, force_text(option_label)
def render_option(self, selected_choices, option_value, option_label):
option_data = self._build_option(selected_choices, option_value, option_label)
return '<option value="%s"%s%s>%s</option>' % option_data
class ApplicationConfigSelect(Select):
"""
Special widget -populate by javascript- that shows application configurations
depending on selected Apphooks.
Required data are injected in the page as javascript data that cms.app_hook_select.js
uses to create the appropriate data structure.
A stub 'add-another' link is created and filled in with the correct URL by the same
javascript.
"""
template_name = 'cms/widgets/applicationconfigselect.html'
class Media:
js = (
static_with_version('cms/js/dist/bundle.forms.apphookselect.min.js'),
)
def __init__(self, attrs=None, choices=(), app_configs={}):
self.app_configs = app_configs
super(ApplicationConfigSelect, self).__init__(attrs, choices)
def _build_script(self, name, value, attrs={}):
configs = []
urls = []
for application, cms_app in self.app_configs.items():
configs.append("'%s': [%s]" % (application, ",".join(
["['%s', '%s']" % (config.pk, escapejs(escape(config))) for config in cms_app.get_configs()]))) # noqa
for application, cms_app in self.app_configs.items():
urls.append("'%s': '%s'" % (application, cms_app.get_config_add_url()))
return r"""<script type="text/javascript">
var apphooks_configuration = {
%(apphooks_configurations)s
};
var apphooks_configuration_url = {
%(apphooks_url)s
};
var apphooks_configuration_value = '%(apphooks_value)s';
</script>""" % {
'apphooks_configurations': ','.join(configs),
'apphooks_url': ','.join(urls),
'apphooks_value': value,
}
def get_context(self, name, value, attrs):
context = super(ApplicationConfigSelect, self).get_context(name, value, attrs)
context['widget']['script_init'] = self._build_script(name, value, context['widget']['attrs'])
return context
-200
View File
@@ -1,200 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django import forms
from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils.text import slugify
from django.utils.translation import (
ugettext,
ugettext_lazy as _,
)
from cms.admin.forms import AddPageForm
from cms.plugin_pool import plugin_pool
from cms.utils import get_current_site, permissions
from cms.utils.page import get_available_slug
from cms.utils.page_permissions import (
user_can_add_page,
user_can_add_subpage,
)
from cms.utils.conf import get_cms_setting
from cms.utils.urlutils import static_with_version
try:
# djangocms_text_ckeditor is not guaranteed to be available
from djangocms_text_ckeditor.widgets import TextEditorWidget
text_widget = TextEditorWidget
except ImportError:
text_widget = forms.Textarea
class SlugWidget(forms.widgets.TextInput):
"""
Special widget for the slug field that requires Title field to be there.
Adds the js for the slugifying.
"""
class Media:
js = (
'admin/js/urlify.js',
static_with_version('cms/js/dist/bundle.forms.slugwidget.min.js'),
)
class CreateCMSPageForm(AddPageForm):
page = None
sub_page_form = False
# Field overrides
menu_title = None
page_title = None
meta_description = None
content = forms.CharField(
label=_(u'Content'), widget=text_widget, required=False,
help_text=_(u"Optional. If supplied, will be automatically added "
u"within a new text plugin.")
)
class Media:
js = (
# This simply adds some JS for
# hiding/showing the content field based on the selection of this select.
'cms/js/widgets/wizard.pagetypeselect.js',
)
def __init__(self, *args, **kwargs):
self._site = get_current_site()
self._user = self.user
self._language = self.language_code
super(CreateCMSPageForm, self).__init__(*args, **kwargs)
self.fields['title'].help_text = _(u"Provide a title for the new page.")
self.fields['slug'].required = False
self.fields['slug'].widget = SlugWidget()
self.fields['slug'].help_text = _(u"Leave empty for automatic slug, or override as required.")
@staticmethod
def get_placeholder(page, slot=None):
"""
Returns the named placeholder or, if no «slot» provided, the first
editable, non-static placeholder or None.
"""
placeholders = page.get_placeholders()
if slot:
placeholders = placeholders.filter(slot=slot)
for ph in placeholders:
if not ph.is_static and ph.is_editable:
return ph
return None
def clean(self):
"""
Validates that either the slug is provided, or that slugification from
`title` produces a valid slug.
:return:
"""
data = self.cleaned_data
if self._errors:
return data
slug = data.get('slug') or slugify(data['title'])
parent_node = data.get('parent_node')
if parent_node:
base = parent_node.item.get_path(self._language)
path = u'%s/%s' % (base, slug) if base else slug
else:
base = ''
path = slug
data['slug'] = get_available_slug(self._site, path, self._language, suffix=None)
data['path'] = '%s/%s' % (base, data['slug']) if base else data['slug']
if not data['slug']:
raise forms.ValidationError("Please provide a valid slug.")
return data
def clean_parent_node(self):
# Check to see if this user has permissions to make this page. We've
# already checked this when producing a list of wizard entries, but this
# is to prevent people from possible form-hacking.
if self.page and self.sub_page_form:
# User is adding a page which will be a direct
# child of the current page.
parent_page = self.page
elif self.page and self.page.parent_page:
# User is adding a page which will be a right
# sibling to the current page.
parent_page = self.page.parent_page
else:
parent_page = None
if parent_page:
has_perm = user_can_add_subpage(self.user, target=parent_page)
else:
has_perm = user_can_add_page(self.user)
if not has_perm:
message = ugettext('You don\'t have the permissions required to add a page.')
raise ValidationError(message)
return parent_page.node if parent_page else None
def clean_slug(self):
# Don't let the PageAddForm validate this
# on the wizard it is not a required field
return self.cleaned_data['slug']
def get_template(self):
return get_cms_setting('PAGE_WIZARD_DEFAULT_TEMPLATE')
@transaction.atomic
def save(self, **kwargs):
from cms.api import add_plugin
new_page = super(CreateCMSPageForm, self).save(**kwargs)
if self.cleaned_data.get("page_type"):
return new_page
parent_node = self.cleaned_data.get('parent_node')
if parent_node and new_page.parent_page.is_page_type:
# the new page was created under a page-type page
# set the new page as a page-type too
new_page.update(
draft_only=True,
is_page_type=True,
in_navigation=False,
)
# If the user provided content, then use that instead.
content = self.cleaned_data.get('content')
plugin_type = get_cms_setting('PAGE_WIZARD_CONTENT_PLUGIN')
plugin_body = get_cms_setting('PAGE_WIZARD_CONTENT_PLUGIN_BODY')
slot = get_cms_setting('PAGE_WIZARD_CONTENT_PLACEHOLDER')
if plugin_type in plugin_pool.plugins and plugin_body:
if content and permissions.has_plugin_permission(
self.user, plugin_type, "add"):
new_page.rescan_placeholders()
placeholder = self.get_placeholder(new_page, slot=slot)
if placeholder:
opts = {
'placeholder': placeholder,
'plugin_type': plugin_type,
'language': self.language_code,
plugin_body: content,
}
add_plugin(**opts)
return new_page
class CreateCMSSubPageForm(CreateCMSPageForm):
sub_page_form = True
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-26
View File
@@ -1,26 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Abdelkader Maarouf <dep2g2@gmail.com>, 2015
# Ahmed H <info@draco-003.com>, 2013
# Bashar Al-Abdulhadi, 2017
# Bashar Ghadanfar <10.tens@gmail.com>, 2017
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: Arabic (http://www.transifex.com/divio/django-cms/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "هل أنت متأكد من الإنتقال الى تبويب أخر دون حفظ الصفحة أولاً؟"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-25
View File
@@ -1,25 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Boris Chervenkov <office@sentido.bg>, 2012
# Boyko Amarov <boyko.amarov@gmail.com>, 2013
# suleyman <posta@suleymans.com>, 2011
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: Bulgarian (http://www.transifex.com/divio/django-cms/language/bg/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: bg\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "Наистина ли искате да промените раздели без първо да запазите страницата?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-23
View File
@@ -1,23 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Aniruddha Adhikary <aniruddha@adhikary.net>, 2013
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: Bengali (http://www.transifex.com/divio/django-cms/language/bn/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: bn\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "আপনি কি নিশ্চিত যে, আপনি পৃষ্ঠাটি সংরক্ষণ না করেই ট্যাব পরিবর্তন করতে চান?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-23
View File
@@ -1,23 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Roger Pons <rogerpons@gmail.com>, 2012,2015
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: Catalan (http://www.transifex.com/divio/django-cms/language/ca/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ca\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "Esteu segur que voleu canviar de secció sense guardar la primera pàgina?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-25
View File
@@ -1,25 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Jakub Dorňák <jakub.dornak@misli.cz>, 2013
# xlu <xlu@seznam.cz>, 2013
# xlu <xlu@seznam.cz>, 2013
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: Czech (http://www.transifex.com/divio/django-cms/language/cs/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs\n"
"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "Určitě chcete změnit záložku bez uložení změn?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-25
View File
@@ -1,25 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Kristian Øllegaard <kristian@oellegaard.com>, 2011
# Kristian Øllegaard <kristian@oellegaard.com>, 2011
# Kristian Øllegaard <kristian@oellegaard.com>, 2011
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: Danish (http://www.transifex.com/divio/django-cms/language/da/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: da\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "Er du sikker på du vil ændre tabs uden at gemme siden først?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-25
View File
@@ -1,25 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Jonas Obrist <me@ojii.ch>, 2011
# Michael P. Jung <michael.jung@terreon.de>, 2013
# Stefan T. Oertel <me@schneck.cc>, 2015
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: German (http://www.transifex.com/divio/django-cms/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "Sind Sie sicher Sie wollen die Sprache ändern ohne vorher zu speichern?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-23
View File
@@ -1,23 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# George Petsagourakis <petsagouris@gmail.com>, 2013
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: Greek (http://www.transifex.com/divio/django-cms/language/el/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: el\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "Είσαι σίγουρος πως θες να αλλάξεις καρτέλες χωρίς να έχεις αποθηκεύσει πρώτα τη σελίδα?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-23
View File
@@ -1,23 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Jonas Obrist <ojiidotch@gmail.com>, 2011
msgid ""
msgstr ""
"Project-Id-Version: django-cms\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2015-10-31 12:28+0000\n"
"Last-Translator: yakky <i.spalletti@nephila.it>\n"
"Language-Team: English (http://www.transifex.com/divio/django-cms/language/"
"en/)\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr "Are you sure you want to change tabs without saving the page first?"
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
-22
View File
@@ -1,22 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: django CMS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-01-29 08:34+0100\n"
"PO-Revision-Date: 2018-01-11 16:38+0000\n"
"Last-Translator: Paulo Alvarado <paulo.alvarado@divio.ch>\n"
"Language-Team: English (United Kingdom) (http://www.transifex.com/divio/django-cms/language/en_GB/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Are you sure you want to change tabs without saving the page first?"
msgstr ""
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More