From e4c50ca7855ee2c65ce6dbb779f7a5648a07f6dd Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 13:42:43 +0300 Subject: [PATCH 01/76] ha --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index f297018c..6ead92a2 100644 --- a/README.rst +++ b/README.rst @@ -18,3 +18,4 @@ Website for ungleich GmbH - /hosting/nodejs/ -- + From fdaf8a18a2fb8a5f19f50cd54aa9226f1a1d4077 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 14:54:38 +0300 Subject: [PATCH 02/76] Fix Docker image build Stop the libldap linker workaround from overwriting the real shared library, and add the missing django-recaptcha requirement. --- Dockerfile | 3 ++- requirements.txt | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4c1a9a66..4e41e75e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,8 @@ RUN apk add --update --no-cache \ ## For alpine 3.15 replace postgresql-dev with libpq-dev # FIX https://github.com/python-ldap/python-ldap/issues/432 -RUN echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so +# (remove the symlink first, otherwise the echo overwrites the real libldap_r shared library) +RUN rm -f /usr/lib/libldap_r.so && echo 'INPUT ( libldap.so )' > /usr/lib/libldap_r.so COPY requirements.txt ./ diff --git a/requirements.txt b/requirements.txt index 73cdf987..db0ab43f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -100,3 +100,4 @@ vine==1.1.4 cdist==5.0.1 git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite pyotp +django-recaptcha From 3c9102167d54e08fc2073d99a2952a57b093502a Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 14:54:38 +0300 Subject: [PATCH 03/76] Make settings tolerate missing environment values env() accepts a default and treats empty values as unset. Values that crashed startup when absent now fall back to safe defaults. --- dynamicweb/settings/base.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index b7705e17..00a77d8b 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -19,8 +19,10 @@ def gettext(s): return s -def env(env_name): - return os.environ.get(env_name) +def env(env_name, default=None): + """Returns the environment value, or default if it is unset or empty""" + value = os.environ.get(env_name) + return default if value is None or value == '' else value def bool_env(val): @@ -560,7 +562,7 @@ STRIPE_API_PRIVATE_KEY_TEST = env('STRIPE_API_PRIVATE_KEY_TEST') ANONYMOUS_USER_NAME = 'anonymous@ungleich.ch' GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance' -UNGLEICH_SITE_CONFIGS = env('UNGLEICH_SITE_CONFIGS') +UNGLEICH_SITE_CONFIGS = env('UNGLEICH_SITE_CONFIGS', '') MULTISITE_CMS_URLS = {} if UNGLEICH_SITE_CONFIGS == "": @@ -579,9 +581,7 @@ else: MULTISITE_CMS_ALIASES = { } -MULTISITE_CMS_FALLBACK = env('MULTISITE_CMS_FALLBACK') -if MULTISITE_CMS_FALLBACK == '': - MULTISITE_CMS_FALLBACK = 'datacenterlight.ch' +MULTISITE_CMS_FALLBACK = env('MULTISITE_CMS_FALLBACK', 'datacenterlight.ch') MULTISITE_FALLBACK = 'django.views.generic.base.RedirectView' MULTISITE_FALLBACK_KWARGS = { 'url': 'https://{}/'.format(MULTISITE_CMS_FALLBACK), 'permanent': False @@ -735,11 +735,11 @@ LDAP_ADMIN_PASSWORD = env('LDAP_ADMIN_PASSWORD') AUTH_LDAP_SERVER = env('LDAPSERVER') LDAP_CUSTOMER_DN = env('LDAP_CUSTOMER_DN') -LDAP_CUSTOMER_GROUP_ID = int(env('LDAP_CUSTOMER_GROUP_ID')) +LDAP_CUSTOMER_GROUP_ID = int(env('LDAP_CUSTOMER_GROUP_ID', 0)) LDAP_MAX_UID_FILE_PATH = os.environ.get('LDAP_MAX_UID_FILE_PATH', os.path.join(os.path.abspath(os.path.dirname(__file__)), 'ldap_max_uid_file') ) -LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID')) +LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID', 10000)) # Search union over OUs AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False)) @@ -761,9 +761,9 @@ OTP_SERVER = env('OTP_SERVER') OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') -PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) +PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE', 0)) -VM_BASE_PRICE = float(env('VM_BASE_PRICE')) +VM_BASE_PRICE = float(env('VM_BASE_PRICE', 0)) UPDATED_TEMPLATES_STR = env('UPDATED_TEMPLATES') UPDATED_TEMPLATES_DICT = {} From 7fdd70022b78e56dceb2c5bbb4c1435847ccf74f Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 14:54:38 +0300 Subject: [PATCH 04/76] Make the djangocms_blog column rename conditional Databases created from scratch already have customuser_id, so the unconditional rename failed on them. --- ...ser_id_to_customer_id_in_djangocms_blog.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/membership/migrations/0008_change_user_id_to_customer_id_in_djangocms_blog.py b/membership/migrations/0008_change_user_id_to_customer_id_in_djangocms_blog.py index 4a8530e9..0eda15cf 100644 --- a/membership/migrations/0008_change_user_id_to_customer_id_in_djangocms_blog.py +++ b/membership/migrations/0008_change_user_id_to_customer_id_in_djangocms_blog.py @@ -1,5 +1,23 @@ from django.db import migrations +# Only rename when the old column exists: databases created from scratch +# already have customuser_id (djangocms_blog is created after AUTH_USER_MODEL +# is set), so an unconditional rename fails there. +RENAME_IF_NEEDED = """ +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'djangocms_blog_authorentriesplugin_authors' + AND column_name = 'user_id' + ) THEN + ALTER TABLE djangocms_blog_authorentriesplugin_authors + RENAME COLUMN user_id TO customuser_id; + END IF; +END +$$; +""" + class Migration(migrations.Migration): dependencies = [('membership', '0007_auto_20180213_0128'), @@ -7,7 +25,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunSQL( - "ALTER TABLE djangocms_blog_authorentriesplugin_authors " - "RENAME COLUMN user_id TO customuser_id;"), + migrations.RunSQL(RENAME_IF_NEEDED), ] From 619a20394eb57d672af50e894938e73c731affec Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 14:54:38 +0300 Subject: [PATCH 05/76] Add docker compose stack for test deployments Runs postgres, redis, the web app and a celery worker. Dev defaults live in .env.docker with an optional .env override. The entrypoint generates and persists the Django secret key and waits for postgres; the web service migrates and creates a Site row for every configured domain. SQL dumps placed in db-init/ are restored on first start and are git-ignored. --- .dockerignore | 3 +++ .env.docker | 41 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 7 +++++++ db-init/README.md | 1 + docker-compose.yml | 42 +++++++++++++++++++++++++++++++++++++++++ entrypoint.sh | 47 ++++++++++++++++++++++++++++++++++++++-------- start-web.sh | 21 +++++++++++++++++++++ 7 files changed, 154 insertions(+), 8 deletions(-) create mode 100644 .env.docker create mode 100644 db-init/README.md create mode 100644 docker-compose.yml create mode 100644 start-web.sh diff --git a/.dockerignore b/.dockerignore index a715c9d7..c78a38ef 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,5 @@ .git .env +*.pyc +__pycache__ +dynamicweb/settings/dynamic.py diff --git a/.env.docker b/.env.docker new file mode 100644 index 00000000..4505a0f2 --- /dev/null +++ b/.env.docker @@ -0,0 +1,41 @@ +# Dev defaults for docker compose. Override any value in a local ".env" file. +# DEBUG=True makes settings load local.py (ALLOWED_HOSTS=*, console email). +DEBUG=True +ENABLE_LOGGING=False +LOG_LEVEL=DEBUG + +# Leave empty: the entrypoint generates and persists one in the "appdata" volume +DJANGO_SECRET_KEY= + +POSTGRES_DB=app +POSTGRES_USER=app +POSTGRES_PASSWORD=app +POSTGRES_HOST=db + +CELERY_BROKER_URL=redis://redis:6379/0 +CELERY_RESULT_BACKEND=redis://redis:6379/0 + +# Host -> urlconf. Add the VPS IP / domain here if you access it that way. +UNGLEICH_SITE_CONFIGS={"ungleich.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"blog.ungleich.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"comic.ungleich.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"digitalglarus.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"datacenterlight.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"rails-hosting.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"django-hosting.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"node-hosting.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"devuanhosting.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"devuanhosting.com":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"ipv6onlyhosting.com":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"digitalezukunft.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"hack4glarus.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"xn--nglarus-n2a.ch":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"localhost:8000":{"MULTISITE_CMS_URL":"dynamicweb.urls"},"127.0.0.1:8000":{"MULTISITE_CMS_URL":"dynamicweb.urls"}} +UNGLEICH_BLOG_SITE_ID=1 +MULTISITE_CMS_FALLBACK= + +EMAIL_HOST=localhost +DCL_ERROR_EMAILS_TO=dev@example.com +ADMIN_EMAIL=dev@example.com +DCL_SUPPORT_FROM_ADDRESS=dev@example.com +WEBHOOK_SECRET=dev +RECAPTCHA_PUBLIC_KEY=dev +RECAPTCHA_PRIVATE_KEY=dev +STRIPE_API_PRIVATE_KEY= +STRIPE_API_PUBLIC_KEY= +STRIPE_API_PRIVATE_KEY_TEST= + +# OpenNebula / LDAP are not available in the test env +OPENNEBULA_USERNAME=oneadmin +OPENNEBULA_PASSWORD=opennebula +OPENNEBULA_PROTOCOL=http +# "test_domain" skips the import-time VMTemplate query (tables do not exist before migrate) +OPENNEBULA_DOMAIN=test_domain +OPENNEBULA_PORT=2633 +OPENNEBULA_ENDPOINT=/RPC2 diff --git a/.gitignore b/.gitignore index a41d813d..fb7ad783 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,10 @@ secret-key 2024-* 2025-* 2023-* + +.env +dynamicweb/settings/dynamic.py + +# database dumps are kept out of git (personal data) +db-init/*.sql +db-init/*.sql.gz diff --git a/db-init/README.md b/db-init/README.md new file mode 100644 index 00000000..812b5d71 --- /dev/null +++ b/db-init/README.md @@ -0,0 +1 @@ +# Put a Postgres dump here (*.sql or *.sql.gz); it is restored on first start. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..3e5d2e68 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,42 @@ +x-app: &app + build: . + env_file: + - .env.docker + - path: .env # optional local overrides + required: false + volumes: + - appdata:/data # generated secret key + - media:/usr/src/app/media + - logs:/usr/src/app/logs + depends_on: [db, redis] + +services: + db: + image: postgres:12-alpine + environment: + POSTGRES_DB: app + POSTGRES_USER: app + POSTGRES_PASSWORD: app + volumes: + - pgdata:/var/lib/postgresql/data + # any *.sql / *.sql.gz here is restored on the first start (empty pgdata) + - ./db-init:/docker-entrypoint-initdb.d:ro + + redis: + image: redis:6-alpine + + web: + <<: *app + command: sh start-web.sh + ports: + - "8000:8000" + + celery: + <<: *app + command: celery -A dynamicweb worker -l info + +volumes: + pgdata: + appdata: + media: + logs: diff --git a/entrypoint.sh b/entrypoint.sh index e207a457..7d024a79 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,19 +1,50 @@ #!/bin/sh -set -uex +set -ue cd /usr/src/app/ -cat > dynamicweb/settings/dynamic.py < "$TMP_KEY" + # hard link is atomic: only one container wins the race + ln "$TMP_KEY" "$KEY_FILE" 2>/dev/null || true + rm -f "$TMP_KEY" + fi + DJANGO_SECRET_KEY="$(cat "$KEY_FILE")" + export DJANGO_SECRET_KEY +fi + +cat > dynamicweb/settings/dynamic.py < Date: Wed, 30 Sep 2026 14:54:38 +0300 Subject: [PATCH 06/76] Add dev site switcher for testing every site from one host With DEBUG on, ?site= selects the site without DNS or hosts entries and is remembered in a cookie. /dev-sites/ lists all configured sites as links. --- dynamicweb/settings/local.py | 4 +++ utils/middleware.py | 55 ++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/dynamicweb/settings/local.py b/dynamicweb/settings/local.py index 4ea3dc7e..c48a4a2b 100644 --- a/dynamicweb/settings/local.py +++ b/dynamicweb/settings/local.py @@ -15,6 +15,10 @@ CACHES = { } } +MIDDLEWARE_CLASSES = ( + 'utils.middleware.DevSiteSwitcherMiddleware', +) + MIDDLEWARE_CLASSES + MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware",) INSTALLED_APPS += ( diff --git a/utils/middleware.py b/utils/middleware.py index 980cc092..40472ac1 100644 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -1,3 +1,9 @@ +import json + +from django.conf import settings +from django.http import HttpResponse + + class MultipleProxyMiddleware(object): FORWARDED_FOR_FIELDS = [ 'HTTP_X_FORWARDED_FOR', @@ -15,3 +21,52 @@ class MultipleProxyMiddleware(object): if ',' in request.META[field]: parts = request.META[field].split(',') request.META[field] = parts[-1].strip() + + +class DevSiteSwitcherMiddleware(object): + """ + Dev/test only (enabled from settings/local.py, i.e. DEBUG=True). + + The project picks a site from the request's Host header. To test every + site from a single address without DNS or /etc/hosts entries, visit + http://:8000/?site=datacenterlight.ch once; the choice is kept in + a cookie. ?site= (empty) clears it. + """ + COOKIE = 'dev_site' + + def process_request(self, request): + if request.path == '/dev-sites/': + return self.sites_page() + if 'site' in request.GET: + site = request.GET['site'].strip() + else: + site = request.COOKIES.get(self.COOKIE, '') + request.META['DEV_SITE_SWITCH'] = site + if site: + request.META['HTTP_HOST'] = site + request.META.pop('HTTP_X_FORWARDED_HOST', None) + + def process_response(self, request, response): + if 'site' in request.GET: + site = request.META.get('DEV_SITE_SWITCH', '') + if site: + response.set_cookie(self.COOKIE, site) + else: + response.delete_cookie(self.COOKIE) + return response + + def sites_page(self): + """A page listing every configured site as a link, on any host""" + try: + domains = sorted( + d for d in json.loads(settings.UNGLEICH_SITE_CONFIGS) + if ':' not in d) + except ValueError: + domains = [] + items = ''.join( + '
  • {0}
  • '.format(d) for d in domains) + return HttpResponse( + 'Test sites' + '

    Test sites

    Click a site to browse it; ' + 'clear the choice.

      {}
    '.format( + items)) From fc4532e89bcdeb97cd587f20c34d6000bdf1ffd3 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 14:54:38 +0300 Subject: [PATCH 07/76] Add guide for running the docker compose stack --- DOCKER.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 DOCKER.md diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 00000000..131e7875 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,93 @@ +# Running dynamicweb with Docker Compose + +A self-contained test/dev stack: Postgres, Redis, the web app and a Celery worker. +Nothing else needs to be installed on the server. + +## Start + +```sh +git clone && cd dynamicweb +docker compose up -d --build +docker compose logs -f web # wait for "Starting development server" +``` + +The first start takes a few minutes: it builds the image, creates the database, +runs all migrations and generates a Django secret key (kept in the `appdata` volume). + +The app listens on port **8000**. Open it at `http://:8000/` +(open the port in the server firewall if needed). + +## Configuration + +- Defaults for everything are in `.env.docker` (committed, dev values only). +- To override a value, create a `.env` file next to `docker-compose.yml` + (optional, git-ignored). Values there win over `.env.docker`. + +### Using your own Postgres instead of the bundled one + +Set the connection in `.env`: + +``` +POSTGRES_HOST= +POSTGRES_PORT=5432 +POSTGRES_DB= +POSTGRES_USER= +POSTGRES_PASSWORD= +``` + +then start everything except the bundled database: + +```sh +docker compose up -d --build --no-deps web celery redis +``` + +## Testing all the sites + +The project serves many websites from one app and chooses the site from the +hostname. In this test setup you don't need DNS or `/etc/hosts`: a dev-only +switcher picks the site from `?site=` and remembers it in a cookie. + +Open **`/dev-sites/`** on any host for a page of clickable links +(`http://:8000/dev-sites/`). On a machine running the stack locally: + +| Site | Link | +|---|---| +| ungleich.ch | http://localhost:8000/?site=ungleich.ch | +| blog.ungleich.ch | http://localhost:8000/?site=blog.ungleich.ch | +| comic.ungleich.ch | http://localhost:8000/?site=comic.ungleich.ch | +| digitalglarus.ch | http://localhost:8000/?site=digitalglarus.ch | +| datacenterlight.ch | http://localhost:8000/?site=datacenterlight.ch | +| rails-hosting.ch | http://localhost:8000/?site=rails-hosting.ch | +| django-hosting.ch | http://localhost:8000/?site=django-hosting.ch | +| node-hosting.ch | http://localhost:8000/?site=node-hosting.ch | +| devuanhosting.ch | http://localhost:8000/?site=devuanhosting.ch | +| devuanhosting.com | http://localhost:8000/?site=devuanhosting.com | +| ipv6onlyhosting.com | http://localhost:8000/?site=ipv6onlyhosting.com | +| digitalezukunft.ch | http://localhost:8000/?site=digitalezukunft.ch | +| hack4glarus.ch | http://localhost:8000/?site=hack4glarus.ch | +| xn--nglarus-n2a.ch | http://localhost:8000/?site=xn--nglarus-n2a.ch | + +Use `?site=` (empty) to clear the choice. To add a site, add it to +`UNGLEICH_SITE_CONFIGS` in `.env.docker` (a `Site` row is created on start). + +## Loading data (optional) + +The database starts empty, so the sites are mostly blank. To load a dump, put a +plain-SQL `*.sql` or `*.sql.gz` file in `db-init/` **before the first start**; Postgres +restores it automatically. To reload: `docker compose down -v` (this deletes the +volumes) and start again. Dumps are git-ignored because they contain personal data. + +## Useful commands + +```sh +docker compose ps # what is running +docker compose logs -f web celery # logs +docker compose up -d --build # rebuild after code changes (code is baked into the image) +docker compose exec web python manage.py createsuperuser +docker compose down # stop (keeps data); add -v to wipe it +``` + +## Limitations of the test setup + +OpenNebula (VM creation), LDAP login and Stripe payments use placeholders and +will not work. Email is printed to the web container log. `DEBUG=True` is on. From 17bf42c8958a30eee4fc4307f3aff404a0f0cbde Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 15:33:02 +0300 Subject: [PATCH 08/76] Read the secret key from the persisted key file when unset docker compose exec skips the entrypoint, so commands run that way had no DJANGO_SECRET_KEY. Fall back to the file the entrypoint writes. --- dynamicweb/settings/base.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 00a77d8b..33213e73 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -79,6 +79,13 @@ EMAIL_PORT = int_env("EMAIL_PORT", 25) EMAIL_USE_TLS = bool_env("EMAIL_USE_TLS") SECRET_KEY = env('DJANGO_SECRET_KEY') +if not SECRET_KEY: + # the docker entrypoint persists a generated key here; this lets + # "docker compose exec" commands, which skip the entrypoint, find it + _key_file = env('SECRET_KEY_FILE', '/data/secret-key') + if os.path.exists(_key_file): + with open(_key_file) as _f: + SECRET_KEY = _f.read().strip() # Application definition From d6653400c63633cfbe96d74f53de0fc4ad3c1f5d Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 15:33:02 +0300 Subject: [PATCH 09/76] Add optional dummy data seeding on start The seed_dummy_data command creates one CMS page per site and a dummy admin and customer, idempotently. --purge removes exactly what it created. It runs on start when SEED_DUMMY_DATA=True and a failure does not stop the web server. --- .env.docker | 6 ++ start-web.sh | 4 + utils/management/__init__.py | 0 utils/management/commands/__init__.py | 0 utils/management/commands/seed_dummy_data.py | 102 +++++++++++++++++++ 5 files changed, 112 insertions(+) create mode 100644 utils/management/__init__.py create mode 100644 utils/management/commands/__init__.py create mode 100644 utils/management/commands/seed_dummy_data.py diff --git a/.env.docker b/.env.docker index 4505a0f2..501c3d9d 100644 --- a/.env.docker +++ b/.env.docker @@ -39,3 +39,9 @@ OPENNEBULA_PROTOCOL=http OPENNEBULA_DOMAIN=test_domain OPENNEBULA_PORT=2633 OPENNEBULA_ENDPOINT=/RPC2 + +# Create dummy pages/users on start (idempotent). Set to False to turn off; +# remove existing dummy data with: docker compose exec web python manage.py seed_dummy_data --purge +SEED_DUMMY_DATA=True +# Leave empty to get a random password printed in the web log on first start +DUMMY_ADMIN_PASSWORD= diff --git a/start-web.sh b/start-web.sh index 7a837d5f..dc2be8d0 100644 --- a/start-web.sh +++ b/start-web.sh @@ -18,4 +18,8 @@ for domain in json.loads(os.environ.get('UNGLEICH_SITE_CONFIGS') or '{}'): Site.objects.get_or_create(domain=domain, defaults={'name': domain}) PYEOF +if [ "${SEED_DUMMY_DATA:-False}" = "True" ]; then + python manage.py seed_dummy_data || echo 'seed_dummy_data failed, continuing' +fi + exec python manage.py runserver 0.0.0.0:8000 diff --git a/utils/management/__init__.py b/utils/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/management/commands/__init__.py b/utils/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/utils/management/commands/seed_dummy_data.py b/utils/management/commands/seed_dummy_data.py new file mode 100644 index 00000000..54da70bf --- /dev/null +++ b/utils/management/commands/seed_dummy_data.py @@ -0,0 +1,102 @@ +""" +Creates dummy data for test environments so that every site shows something +different, or (with --purge) removes exactly what this command created. + +Everything it creates is recognisable and self-contained: + - one published CMS page per site, with reverse_id "dummy-home" + - a dummy admin and a dummy customer, with the emails below +so it can be removed at any time without touching other data. + +Run automatically on start-up when SEED_DUMMY_DATA=True (see start-web.sh). +""" +import json +import os +import random +import string + +from cms.api import add_plugin, create_page +from cms.models import Page +from django.conf import settings +from django.contrib.sites.models import Site +from django.core.management.base import BaseCommand + +from membership.models import CustomUser + +REVERSE_ID = 'dummy-home' +ADMIN_EMAIL = 'dummy-admin@example.com' +CUSTOMER_EMAIL = 'dummy-customer@example.com' +LANGUAGE = 'en-us' +TEMPLATE = 'one_column.html' + + +class Command(BaseCommand): + help = "Create (or, with --purge, remove) dummy data for test environments" + + def add_arguments(self, parser): + parser.add_argument('--purge', action='store_true', + help="remove the dummy data instead of creating it") + + def handle(self, *args, **options): + if options['purge']: + self.purge() + else: + self.seed() + + def purge(self): + pages = Page.objects.filter(reverse_id=REVERSE_ID) + count = pages.count() + for page in pages: + page.delete() + users = CustomUser.objects.filter( + email__in=[ADMIN_EMAIL, CUSTOMER_EMAIL]).delete() + self.stdout.write("Removed {} dummy pages and {} user rows".format( + count, users[0] if isinstance(users, tuple) else users)) + + def seed(self): + self.seed_users() + for domain in self.site_domains(): + site = Site.objects.filter(domain=domain).first() + if site is None: + continue + self.seed_page(site) + self.stdout.write("Dummy data ready") + + def site_domains(self): + configs = json.loads(settings.UNGLEICH_SITE_CONFIGS or '{}') + return sorted(d for d in configs if ':' not in d) + + def seed_users(self): + # built directly on the model: the user manager also creates LDAP + # accounts, which don't exist in the test environment + if not CustomUser.objects.filter(email=ADMIN_EMAIL).exists(): + password = os.environ.get('DUMMY_ADMIN_PASSWORD') or ''.join( + random.SystemRandom().choice(string.ascii_letters + string.digits) + for _ in range(16)) + admin = CustomUser( + email=ADMIN_EMAIL, name='Dummy Admin', validated=1, + is_admin=True, is_superuser=True) + admin.set_password(password) + admin.save() + self.stdout.write( + "Dummy admin: {} / {}".format(ADMIN_EMAIL, password)) + if not CustomUser.objects.filter(email=CUSTOMER_EMAIL).exists(): + customer = CustomUser( + email=CUSTOMER_EMAIL, name='Dummy Customer', validated=1) + customer.set_password('dummy-customer') + customer.save() + + def seed_page(self, site): + if Page.objects.filter(reverse_id=REVERSE_ID, site=site).exists(): + return + page = create_page( + '{} (dummy)'.format(site.domain), TEMPLATE, LANGUAGE, + slug='dummy-home', published=True, in_navigation=True, + site=site, reverse_id=REVERSE_ID) + placeholder = page.placeholders.first() + if placeholder is not None: + add_plugin( + placeholder, 'TextPlugin', LANGUAGE, + body='

    {0}

    Dummy page for {0}.

    '.format( + site.domain)) + # publish again so the public copy contains the plugin + page.publish(LANGUAGE) From 39ba7e5f21488e48af5835ae3432d65f297e4d71 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 15:33:02 +0300 Subject: [PATCH 10/76] Document dummy data in the docker guide --- DOCKER.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index 131e7875..2638f9d3 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -70,12 +70,21 @@ Open **`/dev-sites/`** on any host for a page of clickable links Use `?site=` (empty) to clear the choice. To add a site, add it to `UNGLEICH_SITE_CONFIGS` in `.env.docker` (a `Site` row is created on start). -## Loading data (optional) +## Dummy data -The database starts empty, so the sites are mostly blank. To load a dump, put a -plain-SQL `*.sql` or `*.sql.gz` file in `db-init/` **before the first start**; Postgres -restores it automatically. To reload: `docker compose down -v` (this deletes the -volumes) and start again. Dumps are git-ignored because they contain personal data. +With `SEED_DUMMY_DATA=True` (the default in `.env.docker`), every start creates, +if missing, one dummy CMS page per site (so each site visibly differs) plus a dummy +admin and a dummy customer. It is idempotent, and only touches what it created. + +- The dummy admin is `dummy-admin@example.com`. Its password is random and printed + once in `docker compose logs web` on the first start; set `DUMMY_ADMIN_PASSWORD` + in `.env` to choose it. Log in at `/en-us/admin/login/`. +- Turn it off with `SEED_DUMMY_DATA=False` in `.env`. +- Remove it at any time: `docker compose exec web python manage.py seed_dummy_data --purge`. + +Real data can also be restored from a plain-SQL `*.sql` / `*.sql.gz` dump in `db-init/` +before the first start. To reload: `docker compose down -v` (deletes volumes) and start +again. Dumps are git-ignored because they contain personal data. ## Useful commands From 63c17a6cdf89ba068759f833235a5722b9c16386 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 15:44:43 +0300 Subject: [PATCH 11/76] Choose the dev site from the hostname so tabs stay independent The cookie is shared by all tabs and redirects dropped ?site=, so opening several sites made them all show the last one. The site is now taken from . hosts (each site its own origin), with ?site= and the cookie as fallback, and redirects keep ?site=. /dev-sites/ links use the per-site hostnames, via sslip.io when accessed by IP. --- utils/middleware.py | 86 ++++++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 25 deletions(-) diff --git a/utils/middleware.py b/utils/middleware.py index 40472ac1..da180c5a 100644 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -1,4 +1,5 @@ import json +import re from django.conf import settings from django.http import HttpResponse @@ -28,45 +29,80 @@ class DevSiteSwitcherMiddleware(object): Dev/test only (enabled from settings/local.py, i.e. DEBUG=True). The project picks a site from the request's Host header. To test every - site from a single address without DNS or /etc/hosts entries, visit - http://:8000/?site=datacenterlight.ch once; the choice is kept in - a cookie. ?site= (empty) clears it. + site from one server without DNS or /etc/hosts entries, the site is + chosen, in this order, from: + + 1. the host: ".", e.g. digitalglarus.ch.localhost:8000 + or digitalglarus.ch.203.0.113.5.sslip.io:8000. Every site is then its + own browser origin, so tabs and cookies never interfere. Preferred. + 2. ?site=, remembered in a cookie ("?site=" clears it). Redirects + keep the parameter so the choice survives them. + 3. the cookie set by 2. + + /dev-sites/ lists every configured site as a link. """ COOKIE = 'dev_site' + def domains(self): + try: + found = [d for d in json.loads(settings.UNGLEICH_SITE_CONFIGS) + if ':' not in d] + except ValueError: + found = [] + return sorted(found, key=len, reverse=True) + + def site_from_host(self, request): + host = request.get_host().split(':')[0].lower() + for domain in self.domains(): + if host == domain or host.startswith(domain + '.'): + return domain + return '' + def process_request(self, request): if request.path == '/dev-sites/': - return self.sites_page() - if 'site' in request.GET: - site = request.GET['site'].strip() - else: - site = request.COOKIES.get(self.COOKIE, '') + return self.sites_page(request) + site = self.site_from_host(request) + if not site: + if 'site' in request.GET: + site = request.GET['site'].strip() + else: + site = request.COOKIES.get(self.COOKIE, '') request.META['DEV_SITE_SWITCH'] = site if site: request.META['HTTP_HOST'] = site request.META.pop('HTTP_X_FORWARDED_HOST', None) def process_response(self, request, response): - if 'site' in request.GET: - site = request.META.get('DEV_SITE_SWITCH', '') - if site: - response.set_cookie(self.COOKIE, site) - else: - response.delete_cookie(self.COOKIE) + if 'site' not in request.GET: + return response + site = request.META.get('DEV_SITE_SWITCH', '') + if site: + response.set_cookie(self.COOKIE, site) + location = response.get('Location', '') + if response.status_code in (301, 302) and location.startswith('/') \ + and 'site=' not in location: + response['Location'] = location + ( + '&' if '?' in location else '?') + 'site=' + site + else: + response.delete_cookie(self.COOKIE) return response - def sites_page(self): + def sites_page(self, request): """A page listing every configured site as a link, on any host""" - try: - domains = sorted( - d for d in json.loads(settings.UNGLEICH_SITE_CONFIGS) - if ':' not in d) - except ValueError: - domains = [] + host, _, port = request.get_host().partition(':') + port = ':' + port if port else '' + if re.match(r'^\d+\.\d+\.\d+\.\d+$', host): + # an IP has no subdomains; sslip.io resolves ..sslip.io + base = host + '.sslip.io' + else: + base = host items = ''.join( - '
  • {0}
  • '.format(d) for d in domains) + '
  • {0}
  • '.format( + d, base, port) for d in sorted(self.domains())) return HttpResponse( 'Test sites' - '

    Test sites

    Click a site to browse it; ' - 'clear the choice.

      {}
    '.format( - items)) + '

    Test sites

    Each site opens on its own address, so ' + 'you can open several at once.

      {}
    ' + '

    Fallback without wildcard names: ' + '/?site=<domain> (clear)' + '

    '.format(items)) From e77a17b82f08beed051fa9710978c225148e7af8 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 15:44:43 +0300 Subject: [PATCH 12/76] Seed richer dummy data with a fixed admin login Each site gets a home page and four child pages, and there are twelve dummy customers. The dummy admin password is now fixed through DUMMY_ADMIN_PASSWORD. --purge removes all of it. --- .env.docker | 4 +- utils/management/commands/seed_dummy_data.py | 76 ++++++++++++++------ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/.env.docker b/.env.docker index 501c3d9d..35463a90 100644 --- a/.env.docker +++ b/.env.docker @@ -43,5 +43,5 @@ OPENNEBULA_ENDPOINT=/RPC2 # Create dummy pages/users on start (idempotent). Set to False to turn off; # remove existing dummy data with: docker compose exec web python manage.py seed_dummy_data --purge SEED_DUMMY_DATA=True -# Leave empty to get a random password printed in the web log on first start -DUMMY_ADMIN_PASSWORD= +# Fixed dev login: dummy-admin@example.com / this password +DUMMY_ADMIN_PASSWORD=dummy-admin diff --git a/utils/management/commands/seed_dummy_data.py b/utils/management/commands/seed_dummy_data.py index 54da70bf..d2b70e95 100644 --- a/utils/management/commands/seed_dummy_data.py +++ b/utils/management/commands/seed_dummy_data.py @@ -3,8 +3,10 @@ Creates dummy data for test environments so that every site shows something different, or (with --purge) removes exactly what this command created. Everything it creates is recognisable and self-contained: - - one published CMS page per site, with reverse_id "dummy-home" - - a dummy admin and a dummy customer, with the emails below + - per site, a published home page (reverse_id "dummy-home") with four + child pages (reverse_id "dummy-") + - a dummy admin (fixed login, see DUMMY_ADMIN_PASSWORD) and several dummy + customers, all with dummy-*@example.com emails so it can be removed at any time without touching other data. Run automatically on start-up when SEED_DUMMY_DATA=True (see start-web.sh). @@ -24,7 +26,18 @@ from membership.models import CustomUser REVERSE_ID = 'dummy-home' ADMIN_EMAIL = 'dummy-admin@example.com' -CUSTOMER_EMAIL = 'dummy-customer@example.com' +CUSTOMER_EMAIL_FORMAT = 'dummy-customer-{}@example.com' +CUSTOMER_COUNT = 12 +DEFAULT_ADMIN_PASSWORD = 'dummy-admin' +# (slug, title, text) for the pages below each site's home page +SUB_PAGES = [ + ('about', 'About', 'Who we are and what we do.'), + ('services', 'Services', 'Hosting, servers and managed infrastructure.'), + ('pricing', 'Pricing', 'Simple monthly plans, billed per resource.'), + ('contact', 'Contact', 'Write to us, we answer within one working day.'), +] +FIRST_NAMES = ['Anna', 'Luca', 'Marta', 'Jonas', 'Sofia', 'Noah', 'Elena', + 'Felix', 'Nina', 'Paul', 'Clara', 'Ivan'] LANGUAGE = 'en-us' TEMPLATE = 'one_column.html' @@ -43,14 +56,19 @@ class Command(BaseCommand): self.seed() def purge(self): - pages = Page.objects.filter(reverse_id=REVERSE_ID) - count = pages.count() - for page in pages: + count = Page.objects.filter(reverse_id__startswith='dummy-').count() + # deleting a home page also deletes its children and public copy + for page in Page.objects.filter(reverse_id=REVERSE_ID, + publisher_is_draft=True): + page.delete() + for page in Page.objects.filter(reverse_id__startswith='dummy-'): page.delete() users = CustomUser.objects.filter( - email__in=[ADMIN_EMAIL, CUSTOMER_EMAIL]).delete() - self.stdout.write("Removed {} dummy pages and {} user rows".format( - count, users[0] if isinstance(users, tuple) else users)) + email__startswith='dummy-', email__endswith='@example.com') + user_count = users.count() + users.delete() + self.stdout.write("Removed {} dummy pages and {} users".format( + count, user_count)) def seed(self): self.seed_users() @@ -69,9 +87,8 @@ class Command(BaseCommand): # built directly on the model: the user manager also creates LDAP # accounts, which don't exist in the test environment if not CustomUser.objects.filter(email=ADMIN_EMAIL).exists(): - password = os.environ.get('DUMMY_ADMIN_PASSWORD') or ''.join( - random.SystemRandom().choice(string.ascii_letters + string.digits) - for _ in range(16)) + password = os.environ.get( + 'DUMMY_ADMIN_PASSWORD') or DEFAULT_ADMIN_PASSWORD admin = CustomUser( email=ADMIN_EMAIL, name='Dummy Admin', validated=1, is_admin=True, is_superuser=True) @@ -79,24 +96,37 @@ class Command(BaseCommand): admin.save() self.stdout.write( "Dummy admin: {} / {}".format(ADMIN_EMAIL, password)) - if not CustomUser.objects.filter(email=CUSTOMER_EMAIL).exists(): + for number in range(1, CUSTOMER_COUNT + 1): + email = CUSTOMER_EMAIL_FORMAT.format(number) + if CustomUser.objects.filter(email=email).exists(): + continue customer = CustomUser( - email=CUSTOMER_EMAIL, name='Dummy Customer', validated=1) + email=email, validated=1 if number % 4 else 0, + name='{} Dummy'.format(FIRST_NAMES[number - 1])) customer.set_password('dummy-customer') customer.save() + def add_text(self, page, body): + placeholder = page.placeholders.first() + if placeholder is not None: + add_plugin(placeholder, 'TextPlugin', LANGUAGE, body=body) + # publish again so the public copy contains the plugin + page.publish(LANGUAGE) + def seed_page(self, site): if Page.objects.filter(reverse_id=REVERSE_ID, site=site).exists(): return - page = create_page( + home = create_page( '{} (dummy)'.format(site.domain), TEMPLATE, LANGUAGE, slug='dummy-home', published=True, in_navigation=True, site=site, reverse_id=REVERSE_ID) - placeholder = page.placeholders.first() - if placeholder is not None: - add_plugin( - placeholder, 'TextPlugin', LANGUAGE, - body='

    {0}

    Dummy page for {0}.

    '.format( - site.domain)) - # publish again so the public copy contains the plugin - page.publish(LANGUAGE) + self.add_text(home, '

    {0}

    Dummy page for {0}.

    '.format( + site.domain)) + for slug, title, text in SUB_PAGES: + child = create_page( + title, TEMPLATE, LANGUAGE, slug=slug, published=True, + in_navigation=True, site=site, parent=home, + reverse_id='dummy-' + slug) + self.add_text( + child, '

    {}

    {} ({})

    '.format( + title, text, site.domain)) From 8874fe42980b87e1c6b5308fa89f0229d58361fa Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 15:44:43 +0300 Subject: [PATCH 13/76] Update docker guide for per-site hostnames and richer dummy data --- DOCKER.md | 52 +++++++++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index 2638f9d3..f16fd398 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -45,40 +45,46 @@ docker compose up -d --build --no-deps web celery redis The project serves many websites from one app and chooses the site from the hostname. In this test setup you don't need DNS or `/etc/hosts`: a dev-only -switcher picks the site from `?site=` and remembers it in a cookie. +switcher (active when `DEBUG=True`) reads the site from the address, so every site +is its own browser origin and you can open several at once. -Open **`/dev-sites/`** on any host for a page of clickable links -(`http://:8000/dev-sites/`). On a machine running the stack locally: +- On the machine running the stack, browsers resolve `.localhost` to + the local machine, so the links below just work. +- On a remote server, open **`http://:8000/dev-sites/`**: it lists every site as a + link of the form `http://..sslip.io:8000/` (sslip.io is a public wildcard DNS + service, so the server needs internet access from your browser, not from itself). +- Fallback without wildcard names: `http://:8000/?site=` remembers the + site in a cookie (shared by all tabs, so prefer the addresses above). | Site | Link | |---|---| -| ungleich.ch | http://localhost:8000/?site=ungleich.ch | -| blog.ungleich.ch | http://localhost:8000/?site=blog.ungleich.ch | -| comic.ungleich.ch | http://localhost:8000/?site=comic.ungleich.ch | -| digitalglarus.ch | http://localhost:8000/?site=digitalglarus.ch | -| datacenterlight.ch | http://localhost:8000/?site=datacenterlight.ch | -| rails-hosting.ch | http://localhost:8000/?site=rails-hosting.ch | -| django-hosting.ch | http://localhost:8000/?site=django-hosting.ch | -| node-hosting.ch | http://localhost:8000/?site=node-hosting.ch | -| devuanhosting.ch | http://localhost:8000/?site=devuanhosting.ch | -| devuanhosting.com | http://localhost:8000/?site=devuanhosting.com | -| ipv6onlyhosting.com | http://localhost:8000/?site=ipv6onlyhosting.com | -| digitalezukunft.ch | http://localhost:8000/?site=digitalezukunft.ch | -| hack4glarus.ch | http://localhost:8000/?site=hack4glarus.ch | -| xn--nglarus-n2a.ch | http://localhost:8000/?site=xn--nglarus-n2a.ch | +| ungleich.ch | http://ungleich.ch.localhost:8000/ | +| blog.ungleich.ch | http://blog.ungleich.ch.localhost:8000/ | +| comic.ungleich.ch | http://comic.ungleich.ch.localhost:8000/ | +| digitalglarus.ch | http://digitalglarus.ch.localhost:8000/ | +| datacenterlight.ch | http://datacenterlight.ch.localhost:8000/ | +| rails-hosting.ch | http://rails-hosting.ch.localhost:8000/ | +| django-hosting.ch | http://django-hosting.ch.localhost:8000/ | +| node-hosting.ch | http://node-hosting.ch.localhost:8000/ | +| devuanhosting.ch | http://devuanhosting.ch.localhost:8000/ | +| devuanhosting.com | http://devuanhosting.com.localhost:8000/ | +| ipv6onlyhosting.com | http://ipv6onlyhosting.com.localhost:8000/ | +| digitalezukunft.ch | http://digitalezukunft.ch.localhost:8000/ | +| hack4glarus.ch | http://hack4glarus.ch.localhost:8000/ | +| xn--nglarus-n2a.ch | http://xn--nglarus-n2a.ch.localhost:8000/ | -Use `?site=` (empty) to clear the choice. To add a site, add it to +To add a site, add it to `UNGLEICH_SITE_CONFIGS` in `.env.docker` (a `Site` row is created on start). ## Dummy data With `SEED_DUMMY_DATA=True` (the default in `.env.docker`), every start creates, -if missing, one dummy CMS page per site (so each site visibly differs) plus a dummy -admin and a dummy customer. It is idempotent, and only touches what it created. +if missing, dummy content that mimics production: per site a home page with four child +pages (About, Services, Pricing, Contact), plus a dummy admin and 12 dummy customers. +It is idempotent and only touches what it created. -- The dummy admin is `dummy-admin@example.com`. Its password is random and printed - once in `docker compose logs web` on the first start; set `DUMMY_ADMIN_PASSWORD` - in `.env` to choose it. Log in at `/en-us/admin/login/`. +- Admin login (fixed): `dummy-admin@example.com` / `dummy-admin` at `/en-us/admin/login/`. + Change the password with `DUMMY_ADMIN_PASSWORD` in `.env` (applies when the admin is first created). - Turn it off with `SEED_DUMMY_DATA=False` in `.env`. - Remove it at any time: `docker compose exec web python manage.py seed_dummy_data --purge`. From 4379e9fb89d27f8c2a15465844b30bed2b55c07c Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 15:52:54 +0300 Subject: [PATCH 14/76] Disable caching and report the chosen site in dev responses Adds Cache-Control: no-store and an X-Dev-Site header to the dev site switcher so a browser cannot reuse another site's page or redirect, and the selected site is visible in dev tools. --- utils/middleware.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/middleware.py b/utils/middleware.py index da180c5a..fd6a5851 100644 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -73,9 +73,13 @@ class DevSiteSwitcherMiddleware(object): request.META.pop('HTTP_X_FORWARDED_HOST', None) def process_response(self, request, response): + site = request.META.get('DEV_SITE_SWITCH', '') + # dev only: never let a browser reuse a page or redirect from another + # site, and show which site the server picked (see browser dev tools) + response['Cache-Control'] = 'no-store' + response['X-Dev-Site'] = site or '(default)' if 'site' not in request.GET: return response - site = request.META.get('DEV_SITE_SWITCH', '') if site: response.set_cookie(self.COOKIE, site) location = response.get('Location', '') From 748000afd9fcd33a828b0bf09519430ac6327d19 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 16:03:37 +0300 Subject: [PATCH 15/76] Avoid nested hostnames when /dev-sites/ is opened on a site address --- utils/middleware.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/middleware.py b/utils/middleware.py index fd6a5851..faf3b23a 100644 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -95,6 +95,12 @@ class DevSiteSwitcherMiddleware(object): """A page listing every configured site as a link, on any host""" host, _, port = request.get_host().partition(':') port = ':' + port if port else '' + # opened on a site's own address (e.g. blog.ungleich.ch.localhost): + # drop that site's prefix so links are not nested + for domain in self.domains(): + if host.lower().startswith(domain + '.'): + host = host[len(domain) + 1:] + break if re.match(r'^\d+\.\d+\.\d+\.\d+$', host): # an IP has no subdomains; sslip.io resolves ..sslip.io base = host + '.sslip.io' From 31fe762077111f16e6ae990783f55b1448e48627 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 16:14:54 +0300 Subject: [PATCH 16/76] Seed each site's pages with the CMS template it uses in production --- utils/management/commands/seed_dummy_data.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/utils/management/commands/seed_dummy_data.py b/utils/management/commands/seed_dummy_data.py index d2b70e95..b0034143 100644 --- a/utils/management/commands/seed_dummy_data.py +++ b/utils/management/commands/seed_dummy_data.py @@ -40,6 +40,14 @@ FIRST_NAMES = ['Anna', 'Luca', 'Marta', 'Jonas', 'Sofia', 'Noah', 'Elena', 'Felix', 'Nina', 'Paul', 'Clara', 'Ivan'] LANGUAGE = 'en-us' TEMPLATE = 'one_column.html' +# each site gets the CMS template it uses in production +SITE_TEMPLATES = { + 'ungleich.ch': 'page.html', + 'blog.ungleich.ch': 'page.html', + 'comic.ungleich.ch': 'page.html', + 'datacenterlight.ch': 'datacenterlight/cms/base.html', + 'digitalglarus.ch': 'home_digitalglarus.html', +} class Command(BaseCommand): @@ -116,15 +124,16 @@ class Command(BaseCommand): def seed_page(self, site): if Page.objects.filter(reverse_id=REVERSE_ID, site=site).exists(): return + template = SITE_TEMPLATES.get(site.domain, TEMPLATE) home = create_page( - '{} (dummy)'.format(site.domain), TEMPLATE, LANGUAGE, + '{} (dummy)'.format(site.domain), template, LANGUAGE, slug='dummy-home', published=True, in_navigation=True, site=site, reverse_id=REVERSE_ID) self.add_text(home, '

    {0}

    Dummy page for {0}.

    '.format( site.domain)) for slug, title, text in SUB_PAGES: child = create_page( - title, TEMPLATE, LANGUAGE, slug=slug, published=True, + title, template, LANGUAGE, slug=slug, published=True, in_navigation=True, site=site, parent=home, reverse_id='dummy-' + slug) self.add_text( From 691863b78e24a8a34d1b00c78c7fb3c8ed31184f Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 16:35:15 +0300 Subject: [PATCH 17/76] Keep required settings strict and supply their dev values in .env.docker --- .env.docker | 9 ++++++++- dynamicweb/settings/base.py | 10 +++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.env.docker b/.env.docker index 35463a90..717b5809 100644 --- a/.env.docker +++ b/.env.docker @@ -31,6 +31,12 @@ STRIPE_API_PRIVATE_KEY= STRIPE_API_PUBLIC_KEY= STRIPE_API_PRIVATE_KEY_TEST= +# Values the settings require; the app has no defaults for them +LDAP_CUSTOMER_GROUP_ID=0 +LDAP_DEFAULT_START_UID=10000 +PRE_EU_VAT_RATE=0 +VM_BASE_PRICE=0 + # OpenNebula / LDAP are not available in the test env OPENNEBULA_USERNAME=oneadmin OPENNEBULA_PASSWORD=opennebula @@ -40,7 +46,8 @@ OPENNEBULA_DOMAIN=test_domain OPENNEBULA_PORT=2633 OPENNEBULA_ENDPOINT=/RPC2 -# Create dummy pages/users on start (idempotent). Set to False to turn off; +# Create sample pages/users on start, only into a database without page content +# (idempotent). Set to False to turn off; # remove existing dummy data with: docker compose exec web python manage.py seed_dummy_data --purge SEED_DUMMY_DATA=True # Fixed dev login: dummy-admin@example.com / this password diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 33213e73..8bd24bd5 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -569,7 +569,7 @@ STRIPE_API_PRIVATE_KEY_TEST = env('STRIPE_API_PRIVATE_KEY_TEST') ANONYMOUS_USER_NAME = 'anonymous@ungleich.ch' GUARDIAN_GET_INIT_ANONYMOUS_USER = 'membership.models.get_anonymous_user_instance' -UNGLEICH_SITE_CONFIGS = env('UNGLEICH_SITE_CONFIGS', '') +UNGLEICH_SITE_CONFIGS = env('UNGLEICH_SITE_CONFIGS') MULTISITE_CMS_URLS = {} if UNGLEICH_SITE_CONFIGS == "": @@ -742,11 +742,11 @@ LDAP_ADMIN_PASSWORD = env('LDAP_ADMIN_PASSWORD') AUTH_LDAP_SERVER = env('LDAPSERVER') LDAP_CUSTOMER_DN = env('LDAP_CUSTOMER_DN') -LDAP_CUSTOMER_GROUP_ID = int(env('LDAP_CUSTOMER_GROUP_ID', 0)) +LDAP_CUSTOMER_GROUP_ID = int(env('LDAP_CUSTOMER_GROUP_ID')) LDAP_MAX_UID_FILE_PATH = os.environ.get('LDAP_MAX_UID_FILE_PATH', os.path.join(os.path.abspath(os.path.dirname(__file__)), 'ldap_max_uid_file') ) -LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID', 10000)) +LDAP_DEFAULT_START_UID = int(env('LDAP_DEFAULT_START_UID')) # Search union over OUs AUTH_LDAP_START_TLS = bool(os.environ.get('LDAP_USE_TLS', False)) @@ -768,9 +768,9 @@ OTP_SERVER = env('OTP_SERVER') OTP_VERIFY_ENDPOINT = env('OTP_VERIFY_ENDPOINT') FIRST_VM_ID_AFTER_EU_VAT = int_env('FIRST_VM_ID_AFTER_EU_VAT') -PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE', 0)) +PRE_EU_VAT_RATE = float(env('PRE_EU_VAT_RATE')) -VM_BASE_PRICE = float(env('VM_BASE_PRICE', 0)) +VM_BASE_PRICE = float(env('VM_BASE_PRICE')) UPDATED_TEMPLATES_STR = env('UPDATED_TEMPLATES') UPDATED_TEMPLATES_DICT = {} From fbc6c215ae6212c5bb9127686116dbd565a195bd Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 16:35:15 +0300 Subject: [PATCH 18/76] Seed production-like content, and only into a database without page content --- DOCKER.md | 11 ++-- utils/management/commands/seed_dummy_data.py | 57 +++++++++++++++----- 2 files changed, 50 insertions(+), 18 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index f16fd398..9b5cd2ad 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -78,10 +78,13 @@ To add a site, add it to ## Dummy data -With `SEED_DUMMY_DATA=True` (the default in `.env.docker`), every start creates, -if missing, dummy content that mimics production: per site a home page with four child -pages (About, Services, Pricing, Contact), plus a dummy admin and 12 dummy customers. -It is idempotent and only touches what it created. +With `SEED_DUMMY_DATA=True` (the default in `.env.docker`), a start on a database +without page content creates sample content that mimics production: per site a home page +in that site's own template with four child pages (About, Services, Pricing, Contact), +plus an admin and 12 customers. The text is written to look like a real site, and only +hidden markers (page `reverse_id` starting with `dummy-`, `dummy-*@example.com` users) +identify it. If the database already has page content (for example the owner's own Postgres) +nothing is added. It is idempotent and only touches what it created. - Admin login (fixed): `dummy-admin@example.com` / `dummy-admin` at `/en-us/admin/login/`. Change the password with `DUMMY_ADMIN_PASSWORD` in `.env` (applies when the admin is first created). diff --git a/utils/management/commands/seed_dummy_data.py b/utils/management/commands/seed_dummy_data.py index b0034143..d50fbca6 100644 --- a/utils/management/commands/seed_dummy_data.py +++ b/utils/management/commands/seed_dummy_data.py @@ -1,6 +1,7 @@ """ -Creates dummy data for test environments so that every site shows something -different, or (with --purge) removes exactly what this command created. +Creates sample data for test environments so that every site shows realistic +content of its own (only into a database without page content), or, with +--purge, removes exactly what this command created. Everything it creates is recognisable and self-contained: - per site, a published home page (reverse_id "dummy-home") with four @@ -29,12 +30,29 @@ ADMIN_EMAIL = 'dummy-admin@example.com' CUSTOMER_EMAIL_FORMAT = 'dummy-customer-{}@example.com' CUSTOMER_COUNT = 12 DEFAULT_ADMIN_PASSWORD = 'dummy-admin' +# domain -> (name, tagline); other domains are named after their domain +SITE_TEXTS = { + 'ungleich.ch': ('ungleich', 'Swiss IT infrastructure, hosting and open source.'), + 'blog.ungleich.ch': ('ungleich blog', 'Notes from running data centers, networks and free software.'), + 'comic.ungleich.ch': ('ungleich comic', 'Life in the data center, one panel at a time.'), + 'digitalglarus.ch': ('Digital Glarus', 'The Swiss IT valley in Schwanden, Glarus.'), + 'datacenterlight.ch': ('Data Center Light', 'Virtual machines from a Swiss, ecological data center.'), + 'rails-hosting.ch': ('Rails Hosting', 'Managed hosting for Ruby on Rails applications.'), + 'django-hosting.ch': ('Django Hosting', 'Managed hosting for Django applications.'), + 'node-hosting.ch': ('Node.js Hosting', 'Managed hosting for Node.js applications.'), + 'devuanhosting.ch': ('Devuan Hosting', 'Hosting on Devuan, the systemd-free Linux.'), + 'devuanhosting.com': ('Devuan Hosting', 'Hosting on Devuan, the systemd-free Linux.'), +} # (slug, title, text) for the pages below each site's home page SUB_PAGES = [ - ('about', 'About', 'Who we are and what we do.'), - ('services', 'Services', 'Hosting, servers and managed infrastructure.'), - ('pricing', 'Pricing', 'Simple monthly plans, billed per resource.'), - ('contact', 'Contact', 'Write to us, we answer within one working day.'), + ('about', 'About', '{name} is operated by ungleich GmbH in Switzerland. ' + 'We run our own hardware and publish what we build as free software.'), + ('services', 'Services', 'Virtual machines, managed hosting and ' + 'consulting, all running in our own data centers.'), + ('pricing', 'Pricing', 'Simple monthly plans, billed per resource ' + 'with no minimum term.'), + ('contact', 'Contact', 'Write to support@ungleich.ch and we will ' + 'answer within one working day.'), ] FIRST_NAMES = ['Anna', 'Luca', 'Marta', 'Jonas', 'Sofia', 'Noah', 'Elena', 'Felix', 'Nina', 'Paul', 'Clara', 'Ivan'] @@ -79,6 +97,15 @@ class Command(BaseCommand): count, user_count)) def seed(self): + # the app itself leaves a few empty pages; only pages with content + # mean the database holds real data + real = Page.objects.filter( + publisher_is_draft=True, placeholders__cmsplugin__isnull=False + ).exclude(reverse_id__startswith='dummy-') + if real.exists(): + self.stdout.write( + "Found existing page content, not adding dummy data") + return self.seed_users() for domain in self.site_domains(): site = Site.objects.filter(domain=domain).first() @@ -125,17 +152,19 @@ class Command(BaseCommand): if Page.objects.filter(reverse_id=REVERSE_ID, site=site).exists(): return template = SITE_TEMPLATES.get(site.domain, TEMPLATE) + name, tagline = SITE_TEXTS.get( + site.domain, (site.domain, 'Welcome to {}.'.format(site.domain))) home = create_page( - '{} (dummy)'.format(site.domain), template, LANGUAGE, - slug='dummy-home', published=True, in_navigation=True, - site=site, reverse_id=REVERSE_ID) - self.add_text(home, '

    {0}

    Dummy page for {0}.

    '.format( - site.domain)) + name, template, LANGUAGE, slug='home', meta_description=tagline, + published=True, in_navigation=True, site=site, + reverse_id=REVERSE_ID) + self.add_text(home, '

    {}

    {}

    '.format(name, tagline)) for slug, title, text in SUB_PAGES: child = create_page( - title, template, LANGUAGE, slug=slug, published=True, + title, template, LANGUAGE, slug=slug, + meta_description=text.format(name=name), published=True, in_navigation=True, site=site, parent=home, reverse_id='dummy-' + slug) self.add_text( - child, '

    {}

    {} ({})

    '.format( - title, text, site.domain)) + child, '

    {}

    {}

    '.format( + title, text.format(name=name))) From 920348637e976691315d0cac4317fd48142aaabd Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Wed, 30 Sep 2026 16:49:20 +0300 Subject: [PATCH 19/76] Rewrite docker guide: VPS quick start, production checklist, known limits --- DOCKER.md | 121 +++++++++++++++++++++--------------------------------- 1 file changed, 46 insertions(+), 75 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index 9b5cd2ad..b731693a 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -1,31 +1,41 @@ # Running dynamicweb with Docker Compose -A self-contained test/dev stack: Postgres, Redis, the web app and a Celery worker. -Nothing else needs to be installed on the server. +Postgres, Redis, the web app and a Celery worker in one stack. Only Docker is needed. -## Start +## Quick start (VPS or local) ```sh git clone && cd dynamicweb docker compose up -d --build -docker compose logs -f web # wait for "Starting development server" +docker compose logs -f web # ready when it prints "Starting development server" ``` -The first start takes a few minutes: it builds the image, creates the database, -runs all migrations and generates a Django secret key (kept in the `appdata` volume). +The first start takes a few minutes (image build, migrations, secret key, sample data). +Open port **8000** in the server firewall, then in your browser go to: -The app listens on port **8000**. Open it at `http://:8000/` -(open the port in the server firewall if needed). +- **VPS:** `http://:8000/dev-sites/` +- **Local machine:** `http://localhost:8000/dev-sites/` -## Configuration +That page lists all 14 sites as links. Each opens as its own site, so you can keep several +open in different tabs. (On a VPS the links look like `http://..sslip.io:8000/`; +sslip.io is public wildcard DNS, so your *browser* needs internet, the server doesn't.) -- Defaults for everything are in `.env.docker` (committed, dev values only). -- To override a value, create a `.env` file next to `docker-compose.yml` - (optional, git-ignored). Values there win over `.env.docker`. +Admin login: `dummy-admin@example.com` / `dummy-admin` at `/en-us/admin/login/` +(on any site address). -### Using your own Postgres instead of the bundled one +## What you will see -Set the connection in `.env`: +- Every site has sample pages (Home, About, Services, Pricing, Contact) with realistic text. +- ungleich.ch (and blog, comic), datacenterlight.ch and digitalglarus.ch use their own + design. **The templates of the other 9 sites are not known**, so they show the plain + Digital Glarus page. That is a test-data limit, not a bug: in production every site's + pages and template come from the CMS database. +- OpenNebula (VM creation), LDAP login and Stripe payments are placeholders and will not + work. Email is printed in the web log. + +## Deploying with your own Postgres (production-like) + +Create `.env` next to `docker-compose.yml`; values there override `.env.docker`: ``` POSTGRES_HOST= @@ -33,79 +43,40 @@ POSTGRES_PORT=5432 POSTGRES_DB= POSTGRES_USER= POSTGRES_PASSWORD= +SEED_DUMMY_DATA=False +DEBUG=False +UNGLEICH_SITE_CONFIGS= ``` -then start everything except the bundled database: +plus your real OpenNebula, LDAP, Stripe, email and reCAPTCHA values. Then: ```sh docker compose up -d --build --no-deps web celery redis ``` -## Testing all the sites +Checklist: -The project serves many websites from one app and chooses the site from the -hostname. In this test setup you don't need DNS or `/etc/hosts`: a dev-only -switcher (active when `DEBUG=True`) reads the site from the address, so every site -is its own browser origin and you can open several at once. +- The database already holds the pages, sites and templates (they are stored in the + CMS; nothing is created for you). Sample data is skipped anyway when it finds page content. +- `DEBUG=False` loads the production settings and turns off the `/dev-sites/` switcher, + so reach each site by its real domain name. +- The web container runs Django's `runserver`; put a real web server or proxy in front + for real traffic. -- On the machine running the stack, browsers resolve `.localhost` to - the local machine, so the links below just work. -- On a remote server, open **`http://:8000/dev-sites/`**: it lists every site as a - link of the form `http://..sslip.io:8000/` (sslip.io is a public wildcard DNS - service, so the server needs internet access from your browser, not from itself). -- Fallback without wildcard names: `http://:8000/?site=` remembers the - site in a cookie (shared by all tabs, so prefer the addresses above). +## Configuration -| Site | Link | -|---|---| -| ungleich.ch | http://ungleich.ch.localhost:8000/ | -| blog.ungleich.ch | http://blog.ungleich.ch.localhost:8000/ | -| comic.ungleich.ch | http://comic.ungleich.ch.localhost:8000/ | -| digitalglarus.ch | http://digitalglarus.ch.localhost:8000/ | -| datacenterlight.ch | http://datacenterlight.ch.localhost:8000/ | -| rails-hosting.ch | http://rails-hosting.ch.localhost:8000/ | -| django-hosting.ch | http://django-hosting.ch.localhost:8000/ | -| node-hosting.ch | http://node-hosting.ch.localhost:8000/ | -| devuanhosting.ch | http://devuanhosting.ch.localhost:8000/ | -| devuanhosting.com | http://devuanhosting.com.localhost:8000/ | -| ipv6onlyhosting.com | http://ipv6onlyhosting.com.localhost:8000/ | -| digitalezukunft.ch | http://digitalezukunft.ch.localhost:8000/ | -| hack4glarus.ch | http://hack4glarus.ch.localhost:8000/ | -| xn--nglarus-n2a.ch | http://xn--nglarus-n2a.ch.localhost:8000/ | - -To add a site, add it to -`UNGLEICH_SITE_CONFIGS` in `.env.docker` (a `Site` row is created on start). - -## Dummy data - -With `SEED_DUMMY_DATA=True` (the default in `.env.docker`), a start on a database -without page content creates sample content that mimics production: per site a home page -in that site's own template with four child pages (About, Services, Pricing, Contact), -plus an admin and 12 customers. The text is written to look like a real site, and only -hidden markers (page `reverse_id` starting with `dummy-`, `dummy-*@example.com` users) -identify it. If the database already has page content (for example the owner's own Postgres) -nothing is added. It is idempotent and only touches what it created. - -- Admin login (fixed): `dummy-admin@example.com` / `dummy-admin` at `/en-us/admin/login/`. - Change the password with `DUMMY_ADMIN_PASSWORD` in `.env` (applies when the admin is first created). -- Turn it off with `SEED_DUMMY_DATA=False` in `.env`. -- Remove it at any time: `docker compose exec web python manage.py seed_dummy_data --purge`. - -Real data can also be restored from a plain-SQL `*.sql` / `*.sql.gz` dump in `db-init/` -before the first start. To reload: `docker compose down -v` (deletes volumes) and start -again. Dumps are git-ignored because they contain personal data. +- `.env.docker`: committed dev defaults. `.env`: your overrides (git-ignored). +- Add a site: add it to `UNGLEICH_SITE_CONFIGS` (a `Site` row is created on start). +- Sample data off: `SEED_DUMMY_DATA=False`. Remove it later: + `docker compose exec web python manage.py seed_dummy_data --purge`. +- Restore a real dump: put a `.sql` or `.sql.gz` file in `db-init/`, then + `docker compose down -v` and start again (dumps are git-ignored). ## Useful commands ```sh -docker compose ps # what is running -docker compose logs -f web celery # logs -docker compose up -d --build # rebuild after code changes (code is baked into the image) -docker compose exec web python manage.py createsuperuser -docker compose down # stop (keeps data); add -v to wipe it +docker compose ps # what is running +docker compose logs -f web celery # logs +docker compose up -d --build # after code changes (code is baked into the image) +docker compose down # stop, keep data (-v wipes it) ``` - -## Limitations of the test setup - -OpenNebula (VM creation), LDAP login and Stripe payments use placeholders and -will not work. Email is printed to the web container log. `DEBUG=True` is on. From d4f765c400b318d7b8fb3262c5d781e812266657 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Thu, 1 Oct 2026 13:13:45 +0300 Subject: [PATCH 20/76] Add network configuration to app build in docker-compose --- docker-compose.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 3e5d2e68..80e84a3c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,7 @@ x-app: &app - build: . + build: + context: . + network: host env_file: - .env.docker - path: .env # optional local overrides From 96639c4d4b1dd9886a1eea9426a59f52f4efe659 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Thu, 1 Oct 2026 13:13:49 +0300 Subject: [PATCH 21/76] Update VPS URL in Docker documentation for clarity --- DOCKER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DOCKER.md b/DOCKER.md index b731693a..59b3a737 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -13,7 +13,7 @@ docker compose logs -f web # ready when it prints "Starting development ser The first start takes a few minutes (image build, migrations, secret key, sample data). Open port **8000** in the server firewall, then in your browser go to: -- **VPS:** `http://:8000/dev-sites/` +- **VPS:** `http://185.203.114.159:8000/dev-sites/` - **Local machine:** `http://localhost:8000/dev-sites/` That page lists all 14 sites as links. Each opens as its own site, so you can keep several From 07de15c7953dc817322d51aae3d3098e1ca10b29 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Mon, 5 Oct 2026 22:46:47 +0300 Subject: [PATCH 22/76] Use plain Django login in dev when no LDAP server is configured --- dynamicweb/settings/local.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dynamicweb/settings/local.py b/dynamicweb/settings/local.py index c48a4a2b..28d49bb2 100644 --- a/dynamicweb/settings/local.py +++ b/dynamicweb/settings/local.py @@ -26,3 +26,11 @@ INSTALLED_APPS += ( # debug_toolbar seems to conflict with multisite (and djangocms_multisite) # 'debug_toolbar' ) + +# Without an LDAP server (e.g. the docker test stack) log in with plain Django +# authentication, otherwise every successful login fails creating the LDAP account +if not AUTH_LDAP_SERVER: + AUTHENTICATION_BACKENDS = tuple( + 'django.contrib.auth.backends.ModelBackend' + if backend == 'utils.backend.MyLDAPBackend' else backend + for backend in AUTHENTICATION_BACKENDS) From a0d4ed24975482d377b27d82443237b7e18f421a Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Mon, 5 Oct 2026 22:46:47 +0300 Subject: [PATCH 23/76] Add a smoke test that opens every site and logs in to the admin --- smoke_test.py | 87 ++++++++++++++++++++ utils/management/commands/seed_dummy_data.py | 14 +--- utils/sample_sites.py | 15 ++++ 3 files changed, 103 insertions(+), 13 deletions(-) create mode 100644 smoke_test.py create mode 100644 utils/sample_sites.py diff --git a/smoke_test.py b/smoke_test.py new file mode 100644 index 00000000..981d4bac --- /dev/null +++ b/smoke_test.py @@ -0,0 +1,87 @@ +""" +Smoke test for a running stack: opens every site and the admin login. + + docker compose exec web python smoke_test.py [base_url] + +Needs no extra packages, so it runs the same on every Python/Django version. +Exits non-zero if anything fails. Expects the sample data (SEED_DUMMY_DATA). +""" +import json +import os +import re +import sys +import urllib.error +import urllib.parse +import urllib.request +from http.cookiejar import CookieJar + +from utils.sample_sites import SITE_TEXTS + +BASE = sys.argv[1] if len(sys.argv) > 1 else 'http://127.0.0.1:8000' +ADMIN = ('dummy-admin@example.com', + os.environ.get('DUMMY_ADMIN_PASSWORD') or 'dummy-admin') +SUB_PAGES = ['about', 'services', 'pricing', 'contact'] +failures = [] + + +def fetch(opener, path, host, data=None): + request = urllib.request.Request(BASE + path, data=data) + request.add_header('Host', host) + try: + response = opener.open(request, timeout=60) + except urllib.error.HTTPError as error: + return error.code, error.read().decode('utf-8', 'replace'), path + return (response.status if hasattr(response, 'status') + else response.getcode(), + response.read().decode('utf-8', 'replace'), response.geturl()) + + +def check(name, ok, detail=''): + print('{} {}{}'.format('ok ' if ok else 'FAIL', name, + '' if ok else ' -> ' + detail)) + if not ok: + failures.append(name) + + +def sites(): + configs = json.loads(os.environ.get('UNGLEICH_SITE_CONFIGS') or '{}') + return sorted(d for d in configs if ':' not in d) + + +def main(): + for domain in sites(): + opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(CookieJar())) + name, tagline = SITE_TEXTS.get(domain, (domain, '')) + status, body, _ = fetch(opener, '/', domain) + check('{} /'.format(domain), status == 200, 'status %s' % status) + status, body, _ = fetch(opener, '/en-us/cms/', domain) + check('{} home'.format(domain), + status == 200 and name in body and tagline in body, + 'status %s, content missing' % status) + for slug in SUB_PAGES: + status, _, _ = fetch(opener, '/en-us/cms/%s/' % slug, domain) + check('{} {}'.format(domain, slug), status == 200, + 'status %s' % status) + + # admin login on the first site + domain = sites()[0] + opener = urllib.request.build_opener( + urllib.request.HTTPCookieProcessor(CookieJar())) + status, body, _ = fetch(opener, '/en-us/admin/login/', domain) + token = re.search(r"name=.csrfmiddlewaretoken. value=.([^'\"]+)", body) + check('admin login page', status == 200 and token, 'status %s' % status) + if token: + form = urllib.parse.urlencode({ + 'csrfmiddlewaretoken': token.group(1), 'username': ADMIN[0], + 'password': ADMIN[1], 'next': '/en-us/admin/'}).encode() + status, body, url = fetch( + opener, '/en-us/admin/login/', domain, data=form) + check('admin login works', + status == 200 and 'login' not in url, 'status %s, ended on %s' % (status, url)) + + print('\n{} failure(s)'.format(len(failures))) + sys.exit(1 if failures else 0) + + +main() diff --git a/utils/management/commands/seed_dummy_data.py b/utils/management/commands/seed_dummy_data.py index d50fbca6..8a8c562c 100644 --- a/utils/management/commands/seed_dummy_data.py +++ b/utils/management/commands/seed_dummy_data.py @@ -24,25 +24,13 @@ from django.contrib.sites.models import Site from django.core.management.base import BaseCommand from membership.models import CustomUser +from utils.sample_sites import SITE_TEXTS REVERSE_ID = 'dummy-home' ADMIN_EMAIL = 'dummy-admin@example.com' CUSTOMER_EMAIL_FORMAT = 'dummy-customer-{}@example.com' CUSTOMER_COUNT = 12 DEFAULT_ADMIN_PASSWORD = 'dummy-admin' -# domain -> (name, tagline); other domains are named after their domain -SITE_TEXTS = { - 'ungleich.ch': ('ungleich', 'Swiss IT infrastructure, hosting and open source.'), - 'blog.ungleich.ch': ('ungleich blog', 'Notes from running data centers, networks and free software.'), - 'comic.ungleich.ch': ('ungleich comic', 'Life in the data center, one panel at a time.'), - 'digitalglarus.ch': ('Digital Glarus', 'The Swiss IT valley in Schwanden, Glarus.'), - 'datacenterlight.ch': ('Data Center Light', 'Virtual machines from a Swiss, ecological data center.'), - 'rails-hosting.ch': ('Rails Hosting', 'Managed hosting for Ruby on Rails applications.'), - 'django-hosting.ch': ('Django Hosting', 'Managed hosting for Django applications.'), - 'node-hosting.ch': ('Node.js Hosting', 'Managed hosting for Node.js applications.'), - 'devuanhosting.ch': ('Devuan Hosting', 'Hosting on Devuan, the systemd-free Linux.'), - 'devuanhosting.com': ('Devuan Hosting', 'Hosting on Devuan, the systemd-free Linux.'), -} # (slug, title, text) for the pages below each site's home page SUB_PAGES = [ ('about', 'About', '{name} is operated by ungleich GmbH in Switzerland. ' diff --git a/utils/sample_sites.py b/utils/sample_sites.py new file mode 100644 index 00000000..09fd3b74 --- /dev/null +++ b/utils/sample_sites.py @@ -0,0 +1,15 @@ +"""Sample site names and taglines, shared by the seeder and smoke_test.py. +No Django imports, so it works anywhere.""" +# domain -> (name, tagline); other domains are named after their domain +SITE_TEXTS = { + 'ungleich.ch': ('ungleich', 'Swiss IT infrastructure, hosting and open source.'), + 'blog.ungleich.ch': ('ungleich blog', 'Notes from running data centers, networks and free software.'), + 'comic.ungleich.ch': ('ungleich comic', 'Life in the data center, one panel at a time.'), + 'digitalglarus.ch': ('Digital Glarus', 'The Swiss IT valley in Schwanden, Glarus.'), + 'datacenterlight.ch': ('Data Center Light', 'Virtual machines from a Swiss, ecological data center.'), + 'rails-hosting.ch': ('Rails Hosting', 'Managed hosting for Ruby on Rails applications.'), + 'django-hosting.ch': ('Django Hosting', 'Managed hosting for Django applications.'), + 'node-hosting.ch': ('Node.js Hosting', 'Managed hosting for Node.js applications.'), + 'devuanhosting.ch': ('Devuan Hosting', 'Hosting on Devuan, the systemd-free Linux.'), + 'devuanhosting.com': ('Devuan Hosting', 'Hosting on Devuan, the systemd-free Linux.'), +} From 2beebb9c9f77d5ce704ecbc866176e8756578ecd Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Mon, 5 Oct 2026 22:46:47 +0300 Subject: [PATCH 24/76] Document the startup delay, the smoke test and the login behavior --- DOCKER.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index 59b3a737..eb853b29 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -11,6 +11,7 @@ docker compose logs -f web # ready when it prints "Starting development ser ``` The first start takes a few minutes (image build, migrations, secret key, sample data). +`docker compose ps` shows "Up" about 1.5 minutes before the site answers, so wait for the log line. Open port **8000** in the server firewall, then in your browser go to: - **VPS:** `http://185.203.114.159:8000/dev-sites/` @@ -30,8 +31,8 @@ Admin login: `dummy-admin@example.com` / `dummy-admin` at `/en-us/admin/login/` design. **The templates of the other 9 sites are not known**, so they show the plain Digital Glarus page. That is a test-data limit, not a bug: in production every site's pages and template come from the CMS database. -- OpenNebula (VM creation), LDAP login and Stripe payments are placeholders and will not - work. Email is printed in the web log. +- OpenNebula (VM creation), LDAP accounts and Stripe payments are placeholders and will not + work (logins use plain Django authentication when no LDAP server is set). Email is printed in the web log. ## Deploying with your own Postgres (production-like) @@ -72,6 +73,14 @@ Checklist: - Restore a real dump: put a `.sql` or `.sql.gz` file in `db-init/`, then `docker compose down -v` and start again (dumps are git-ignored). +## Checking that everything works + +```sh +docker compose exec web python smoke_test.py +``` + +Opens all sites, their pages and the admin login, and exits non-zero on any failure. + ## Useful commands ```sh From b003bbcc52fc42e8c2426316b277f05ca5840c1d Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Mon, 5 Oct 2026 22:59:58 +0300 Subject: [PATCH 25/76] Add a Django 1.9 sample-database snapshot for testing migrations --- legacy-snapshot/django-1.9.sql.gz | Bin 0 -> 68333 bytes legacy-snapshot/seed_extra.py | 48 ++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 legacy-snapshot/django-1.9.sql.gz create mode 100644 legacy-snapshot/seed_extra.py diff --git a/legacy-snapshot/django-1.9.sql.gz b/legacy-snapshot/django-1.9.sql.gz new file mode 100644 index 0000000000000000000000000000000000000000..4e817d374586db7cc1984272ea471086341dd53b GIT binary patch literal 68333 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�Zbi1oZ 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 SU3tzcmHY>=+evOvuLuCVxaNld literal 0 HcmV?d00001 diff --git a/legacy-snapshot/seed_extra.py b/legacy-snapshot/seed_extra.py new file mode 100644 index 00000000..f9c368f1 --- /dev/null +++ b/legacy-snapshot/seed_extra.py @@ -0,0 +1,48 @@ +# One-off: adds the kinds of rows a long-lived installation has (blog posts, +# contact messages, hosting plans, supporters) on top of the sample data. +# Run on the original Django 1.9 stack, see legacy-snapshot/README.md. +from django.contrib.sites.models import Site +from django.utils import timezone +from djangocms_blog.models import BlogCategory, BlogConfig, Post + +from datacenterlight.models import ContactUs +from digitalglarus.models import MembershipType, Supporter +from hosting.models import HostingPlan +from membership.models import CustomUser +from utils.models import ContactMessage + +# start clean, so the script can be re-run +for model in (Post, BlogCategory, ContactMessage, ContactUs, HostingPlan, + Supporter): + model.objects.all().delete() + +config = BlogConfig.objects.first() +author = CustomUser.objects.get(email='dummy-admin@example.com') +category = BlogCategory.objects.language('en').create( + name='Operations', app_config=config) +for number in range(1, 9): + post = Post.objects.language('en').create( + title='Operations note {}'.format(number), + slug='operations-note-{}'.format(number), + abstract='

    Short summary of note {}.

    '.format(number), + app_config=config, author=author, publish=True, + date_published=timezone.now()) + post.categories.add(category) + post.sites.add(Site.objects.get(domain='blog.ungleich.ch')) +for number in range(1, 9): + ContactMessage.objects.create( + name='Visitor {}'.format(number), + email='visitor-{}@example.com'.format(number), + message='Question number {} about hosting.'.format(number)) +for number in range(1, 6): + ContactUs.objects.create( + name='Prospect {}'.format(number), + email='prospect-{}@example.com'.format(number), + message='Please send me an offer, request {}.'.format(number)) +for cores, memory, disk in [(1, 1, 10), (2, 4, 40), (4, 8, 100)]: + HostingPlan.objects.create( + cpu_cores=cores, memory=memory, disk_size=disk) +for name in ['Hack4Glarus', 'Alpine Cloud', 'Linux Verein']: + Supporter.objects.create(name=name, description='Supports the project.') +MembershipType.objects.get_or_create(name='standard', defaults={'price': 20}) +print('legacy extras ready') From 8c1cfcfc112398c5fc8f9c5c5de4dcdab9d6151f Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Mon, 5 Oct 2026 22:59:58 +0300 Subject: [PATCH 26/76] Add a script to restore the snapshot into the compose stack --- legacy-snapshot/README.md | 15 +++++++++++++++ legacy-snapshot/restore.sh | 14 ++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 legacy-snapshot/README.md create mode 100644 legacy-snapshot/restore.sh diff --git a/legacy-snapshot/README.md b/legacy-snapshot/README.md new file mode 100644 index 00000000..22722bec --- /dev/null +++ b/legacy-snapshot/README.md @@ -0,0 +1,15 @@ +# Old-version database snapshot + +`django-1.9.sql.gz` is a dump of the sample database made on the original +Django 1.9 stack (no real or personal data). It stands in for an old +production database: after each upgrade step, restore it and let the new +version migrate it. + +```sh +sh legacy-snapshot/restore.sh +docker compose exec web python smoke_test.py # after the site answers (~1.5 min) +``` + +It holds the sample sites and pages from `seed_dummy_data` plus what +`seed_extra.py` adds: blog posts, contact messages, hosting plans and +supporters. Real production rows are the one thing it cannot cover. diff --git a/legacy-snapshot/restore.sh b/legacy-snapshot/restore.sh new file mode 100644 index 00000000..3e7068c5 --- /dev/null +++ b/legacy-snapshot/restore.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Replaces the database of the running compose stack with the Django 1.9 +# snapshot, then restarts the app (its start-up runs "migrate"). +# Use it to check that an upgraded version can migrate old data: +# sh legacy-snapshot/restore.sh +set -e +cd "$(dirname "$0")/.." +docker compose stop web celery +docker compose exec -T db psql -U app -d postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'app' AND pid <> pg_backend_pid()" > /dev/null +docker compose exec -T db psql -U app -d postgres -c "DROP DATABASE IF EXISTS app" +docker compose exec -T db psql -U app -d postgres -c "CREATE DATABASE app OWNER app" +gunzip -c legacy-snapshot/django-1.9.sql.gz | docker compose exec -T db psql -U app -d app -q -v ON_ERROR_STOP=1 > /dev/null +docker compose up -d web celery +echo "Restored. The site answers after about 1.5 minutes; then run: docker compose exec web python smoke_test.py" From b4e73d50c6a43e84af8a5354ab79b8cf22eb04d1 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 01:10:05 +0300 Subject: [PATCH 27/76] Pin dependencies for Django 1.11 --- requirements.txt | 199 +++++++++++++++++++++++++---------------------- 1 file changed, 107 insertions(+), 92 deletions(-) diff --git a/requirements.txt b/requirements.txt index db0ab43f..bff8290c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,103 +1,118 @@ -Babel==2.3.4 -Django==1.9.4 -Django-Select2==4.3.2 -Pillow==3.2.0 -URLObject==2.4.0 -Unidecode==0.04.19 -YURL==0.13 -aldryn-apphooks-config==0.2.7 -aldryn-boilerplates==0.7.4 -aldryn-categories==1.0.3 -aldryn-common==1.0.1 -aldryn-newsblog==1.2.1 -aldryn-people==1.2.0 -aldryn-reversion==1.0.8 -aldryn-search==0.2.11 -aldryn-translation-tools==0.2.1 +Babel==2.7.0 +Django==1.11.29 +Django-Select2==6.3.1 +Pillow==6.0.0 +URLObject==2.4.3 +Unidecode==1.0.23 +YURL==1.0.0 +aldryn-apphooks-config==0.5.2 +aldryn-boilerplates==0.8.0 +aldryn-categories==1.2.0 +aldryn-common==1.0.5 +aldryn-newsblog==2.2.1 +aldryn-people==2.2.0 +aldryn-search==1.0.1 +aldryn-translation-tools==0.3.0 backport-collections==0.1 -cmsplugin-filer==1.0.1 -django-appconf==1.0.2 -django-appdata==0.1.5 -django-bootstrap3==7.0.1 -django-classy-tags==0.7.2 -django-cms==3.2.5 -django-compressor==2.0 -django-debug-toolbar==1.4 +cmsplugin-filer==1.1.3 +django-appconf==1.0.3 +django-appdata==0.2.1 +django-bootstrap3==11.0.0 +django-classy-tags==0.9.0 +django-cms==3.6.0 +django-compressor==2.3 +django-debug-toolbar==2.0 python-dotenv==0.10.3 -django-extensions==1.6.7 -django-filer==1.2.0 -django-filter==0.13.0 -django-formtools==1.0 -django-guardian==1.4.4 -django-haystack==2.4.1 -django-meta==1.2 -django-meta-mixin==0.3.0 -django-model-utils==2.5 -django-mptt==0.8.4 -django-parler==1.6.3 -django-phonenumber-field==1.1.0 -django-polymorphic==0.9.2 -django-reversion==1.10.2 -django-sekizai==0.9.0 -django-sortedm2m==1.2.2 -django-spurl==0.6.4 +django-extensions==2.1.9 +django-filer==1.5.0 +django-filter==2.1.0 +django-formtools==2.1 +django-guardian==2.0.0 +django-haystack==2.8.1 +django-meta==1.5.1 +django-model-utils==3.2.0 +django-mptt==0.10.0 +django-parler==1.9.2 +django-phonenumber-field==1.3.0 +django-polymorphic==2.0.3 +django-reversion==2.0.13 +django-sekizai==1.0.0 +django-sortedm2m==1.5.0 +django-spurl==0.6.6 django-standard-form==1.1.1 django-stored-messages==1.4.0 -django-taggit==0.18.3 -django-taggit-autosuggest==0.2.8 +django-taggit==0.23.0 +django-taggit-autosuggest==0.3.7 django-taggit-templatetags==0.2.5 django-templatetag-sugar==1.0 -django-treebeard==4.0.1 -djangocms-admin-style==1.1.1 -djangocms-apphook-setup==0.1.2 -djangocms-blog==0.9.0 -djangocms-file==1.0 +django-treebeard==4.3 +djangocms-admin-style==1.4.0 +djangocms-apphook-setup==0.3.0 +djangocms-blog==0.9.9 +djangocms-file==2.3.0 djangocms-flash==0.3.0 -djangocms-googlemap==0.4.0 -djangocms-inherit==0.2.1 -djangocms-link==1.7.2 -djangocms-page-meta==0.5.11 -djangocms-picture==1.0.0 +djangocms-googlemap==1.3.0 +djangocms-inherit==0.2.2 +djangocms-link==2.4.0 +djangocms-page-meta==0.8.5 +djangocms-picture==2.2.1 djangocms-teaser==0.2.0 -djangocms-text-ckeditor==2.9.3 -djangocms-video==1.0.0 -easy-thumbnails==2.3 -html5lib==0.9999999 -ldap3==2.6.1 -lxml==3.6.0 -model-mommy==1.2.6 -phonenumbers==7.4.0 -phonenumberslite==7.4.0 -psycopg2==2.7.3.2 -pycryptodome==3.6.6 -pylibmc==1.5.1 -python-dateutil==2.5.3 -python-slugify==1.2.0 -pytz==2016.4 +djangocms-text-ckeditor==3.8.0 +djangocms-video==2.1.1 +easy-thumbnails==2.6 +html5lib==1.0.1 +ldap3==2.6 +lxml==4.3.4 +model-mommy==1.6.0 +phonenumbers==8.10.14 +phonenumberslite==8.10.14 +psycopg2==2.7.7 +pycryptodome==3.8.2 +pylibmc==1.6.0 +python-dateutil==2.8.0 +python-slugify==3.0.2 +pytz==2019.1 rcssmin==1.0.6 -requests==2.10.0 -rjsmin==1.0.12 -six==1.10.0 -sqlparse==0.1.19 -stripe==2.41.0 -wheel==0.29.0 -django-admin-honeypot==1.0.0 -coverage==4.3.4 -git+https://github.com/ungleich/python-oca.git#egg=oca -djangorestframework==3.6.3 -flake8==3.3.0 -python-memcached==1.58 -celery==4.0.2 -redis==2.10.5 -django-celery-results==1.0.1 -kombu==4.1.0 +requests==2.22.0 +rjsmin==1.1.0 +six==1.12.0 +sqlparse==0.3.0 +stripe==2.32.0 +wheel==0.33.4 +django-admin-honeypot==1.1.0 +coverage==4.5.3 +djangorestframework==3.9.4 +flake8==3.7.7 +python-memcached==1.59 +celery==4.3.0 +redis==3.2.1 +django-celery-results==1.1.2 +kombu==4.6.3 mccabe==0.6.1 -pycodestyle==2.3.1 -pyflakes==1.5.0 -billiard==3.5.0.3 -amqp==2.2.1 -vine==1.1.4 -cdist==5.0.1 +pycodestyle==2.5.0 +pyflakes==2.1.1 +billiard==3.6.0.0 +amqp==2.5.0 +vine==1.3.0 +cdist==5.1.2 +pyotp==2.2.7 +django-recaptcha==2.0.4 +git+https://github.com/ungleich/python-oca.git#egg=oca git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite -pyotp -django-recaptcha +# transitive dependencies, pinned for reproducible builds +certifi==2021.10.8 +chardet==3.0.4 +django-js-asset==1.2.3 +django-multisite==1.9.0 +djangocms-attributes-field==1.2.0 +entrypoints==0.3 +idna==2.8 +importlib-metadata==2.1.3 +pluggy==0.13.1 +pyasn1==0.4.8 +requests-file==3.0.1 +text-unidecode==1.2 +tldextract==2.2.3 +urllib3==1.25.11 +webencodings==0.5.1 +zipp==1.2.0 From 8e5761e62a6b1ed4aa3a651d7b668f0c8a356a05 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 01:10:06 +0300 Subject: [PATCH 28/76] Move settings and middleware to Django 1.11 style Use MIDDLEWARE with adapted old-style middleware, the template context processors, explicit bootstrap3 URLs, the captcha app name, and drop the obsolete migration module overrides and removed apps. --- dynamicweb/settings-test/__init__.py | 41 +++++-------------------- dynamicweb/settings/base.py | 45 +++++----------------------- dynamicweb/settings/local.py | 6 ++-- utils/forms.py | 2 +- utils/middleware.py | 11 +++++-- 5 files changed, 28 insertions(+), 77 deletions(-) diff --git a/dynamicweb/settings-test/__init__.py b/dynamicweb/settings-test/__init__.py index 1a8babdd..fcff0879 100644 --- a/dynamicweb/settings-test/__init__.py +++ b/dynamicweb/settings-test/__init__.py @@ -85,7 +85,6 @@ INSTALLED_APPS = ( 'taggit_autosuggest', 'django_select2', 'meta', - 'meta_mixin', # 'admin_enhancer', 'djangocms_blog', 'bootstrap3', @@ -96,12 +95,11 @@ INSTALLED_APPS = ( 'digitalglarus', ) -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.locale.LocaleMiddleware', @@ -137,13 +135,13 @@ WSGI_APPLICATION = 'dynamicweb.wsgi.application' # items to be merged into the context. TEMPLATE_CONTEXT_PROCESSORS = ( "django.contrib.auth.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.core.context_processors.tz", + "django.template.context_processors.debug", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", - "django.core.context_processors.request", + "django.template.context_processors.request", 'sekizai.context_processors.sekizai', 'cms.context_processors.cms_settings', ) @@ -284,31 +282,6 @@ TEMPLATE_DIRS = ( META_SITE_PROTOCOL = 'http' META_USE_SITES = True -MIGRATION_MODULES = { - 'cms': 'cms.migrations', - 'filer': 'filer.migrations_django', - 'menus': 'menus.migrations_django', - 'djangocms_flash': 'djangocms_flash.migrations_django', - 'djangocms_googlemap': 'djangocms_googlemap.migrations_django', - 'djangocms_inherit': 'djangocms_inherit.migrations_django', - 'djangocms_link': 'djangocms_link.migrations_django', - 'djangocms_snippet': 'djangocms_snippet.migrations_django', - 'djangocms_teaser': 'djangocms_teaser.migrations_django', - 'djangocms_column': 'djangocms_column.migrations_django', - 'djangocms_flash': 'djangocms_flash.migrations_django', - 'djangocms_googlemap': 'djangocms_googlemap.migrations_django', - 'djangocms_inherit': 'djangocms_inherit.migrations_django', - 'djangocms_style': 'djangocms_style.migrations_django', - 'cmsplugin_filer_image': 'cmsplugin_filer_image.migrations_django', - 'cmsplugin_filer_file': 'cmsplugin_filer_file.migrations_django', - 'cmsplugin_filer_folder': 'cmsplugin_filer_folder.migrations_django', - 'cmsplugin_filer_link': 'cmsplugin_filer_link.migrations_django', - 'cmsplugin_filer_teaser': 'cmsplugin_filer_teaser.migrations_django', - 'cmsplugin_filer_utils': 'cmsplugin_filer_utils.migrations_django', - 'cmsplugin_filer_video': 'cmsplugin_filer_video.migrations_django', - 'djangocms_text_ckeditor': 'djangocms_text_ckeditor.migrations', -} - STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 8bd24bd5..96c254e3 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -113,7 +113,6 @@ INSTALLED_APPS = ( 'taggit_autosuggest', # 'django_select2', 'meta', - 'meta_mixin', 'bootstrap3', 'compressor', 'filer', @@ -125,7 +124,6 @@ INSTALLED_APPS = ( 'aldryn_common', 'aldryn_newsblog', 'aldryn_people', - 'aldryn_reversion', 'aldryn_translation_tools', 'treebeard', # utilities for implementing a tree 'sekizai', # for javascript and css management @@ -137,7 +135,7 @@ INSTALLED_APPS = ( 'djangocms_file', 'djangocms_picture', 'djangocms_video', - 'django_recaptcha', + 'captcha', # 'djangocms_flash', # 'djangocms_googlemap', # 'djangocms_inherit', @@ -169,13 +167,12 @@ INSTALLED_APPS = ( 'webhook', ) -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'utils.middleware.MultipleProxyMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.locale.LocaleMiddleware', @@ -185,7 +182,7 @@ MIDDLEWARE_CLASSES = ( 'cms.middleware.toolbar.ToolbarMiddleware', 'cms.middleware.language.LanguageCookieMiddleware', 'multisite.middleware.DynamicSiteMiddleware', - 'djangocms_multisite.middleware.CMSMultiSiteMiddleware', + 'utils.middleware.CMSMultiSiteMiddleware', ) CSRF_FAILURE_VIEW = 'hosting.views.forbidden_view' @@ -217,9 +214,9 @@ TEMPLATES = [ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', - "django.core.context_processors.media", - "django.core.context_processors.static", - "django.core.context_processors.tz", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", "django.contrib.messages.context_processors.messages", 'sekizai.context_processors.sekizai', 'cms.context_processors.cms_settings', @@ -415,27 +412,6 @@ MEDIA_ROOT = os.path.join(PROJECT_DIR, 'media') MEDIA_URL = APP_ROOT_ENDPOINT + 'media/' FILE_UPLOAD_PERMISSIONS = 0o644 -MIGRATION_MODULES = { - 'cms': 'cms.migrations', - # 'filer': 'filer.migrations_django', - # 'menus': 'menus.migrations_django', - 'djangocms_flash': 'djangocms_flash.migrations_django', - 'djangocms_googlemap': 'djangocms_googlemap.migrations_django', - 'djangocms_inherit': 'djangocms_inherit.migrations_django', - 'djangocms_link': 'djangocms_link.migrations_django', - 'djangocms_teaser': 'djangocms_teaser.migrations_django', - 'djangocms_column': 'djangocms_column.migrations_django', - 'djangocms_style': 'djangocms_style.migrations_django', - 'cmsplugin_filer_image': 'cmsplugin_filer_image.migrations_django', - 'cmsplugin_filer_file': 'cmsplugin_filer_file.migrations_django', - 'cmsplugin_filer_folder': 'cmsplugin_filer_folder.migrations_django', - 'cmsplugin_filer_link': 'cmsplugin_filer_link.migrations_django', - 'cmsplugin_filer_teaser': 'cmsplugin_filer_teaser.migrations_django', - 'cmsplugin_filer_utils': 'cmsplugin_filer_utils.migrations_django', - 'cmsplugin_filer_video': 'cmsplugin_filer_video.migrations_django', - 'djangocms_text_ckeditor': 'djangocms_text_ckeditor.migrations', -} - STATICFILES_FINDERS = ( 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', @@ -468,19 +444,14 @@ BOOTSTRAP3 = { # The URL to the jQuery JavaScript file 'jquery_url': '%sdatacenterlight/js/jquery-1.11.1.min.js' % STATIC_URL, - # The Bootstrap base URL - 'base_url': '%sdatacenterlight/bootstrap-3.3.4/' % STATIC_URL, - # The complete URL to the Bootstrap CSS file - # (None means derive it from base_url) - 'css_url': None, + 'css_url': '%sdatacenterlight/bootstrap-3.3.4/css/bootstrap.min.css' % STATIC_URL, # The complete URL to the Bootstrap CSS file (None means no theme) 'theme_url': None, # The complete URL to the Bootstrap JavaScript file - # (None means derive it from base_url) - 'javascript_url': None, + 'javascript_url': '%sdatacenterlight/bootstrap-3.3.4/js/bootstrap.min.js' % STATIC_URL, # Put JavaScript in the HEAD section of the HTML document # (only relevant if you use bootstrap3.html) diff --git a/dynamicweb/settings/local.py b/dynamicweb/settings/local.py index 28d49bb2..3bd04f63 100644 --- a/dynamicweb/settings/local.py +++ b/dynamicweb/settings/local.py @@ -15,11 +15,11 @@ CACHES = { } } -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = ( 'utils.middleware.DevSiteSwitcherMiddleware', -) + MIDDLEWARE_CLASSES +) + MIDDLEWARE -MIDDLEWARE_CLASSES += ("debug_toolbar.middleware.DebugToolbarMiddleware",) +MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",) INSTALLED_APPS += ( 'django_extensions', diff --git a/utils/forms.py b/utils/forms.py index 3cc57578..50161a11 100644 --- a/utils/forms.py +++ b/utils/forms.py @@ -4,7 +4,7 @@ from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils.translation import ugettext_lazy as _ -from django_recaptcha.fields import ReCaptchaField +from captcha.fields import ReCaptchaField from membership.models import CustomUser from .models import ContactMessage, BillingAddress, UserBillingAddress diff --git a/utils/middleware.py b/utils/middleware.py index faf3b23a..103ae3d6 100644 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -3,9 +3,16 @@ import re from django.conf import settings from django.http import HttpResponse +from django.utils.deprecation import MiddlewareMixin + +from djangocms_multisite.middleware import CMSMultiSiteMiddleware as _CMSMultiSite -class MultipleProxyMiddleware(object): +class CMSMultiSiteMiddleware(MiddlewareMixin, _CMSMultiSite): + """The package's middleware is old-style (no get_response); adapt it""" + + +class MultipleProxyMiddleware(MiddlewareMixin): FORWARDED_FOR_FIELDS = [ 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED_HOST', @@ -24,7 +31,7 @@ class MultipleProxyMiddleware(object): request.META[field] = parts[-1].strip() -class DevSiteSwitcherMiddleware(object): +class DevSiteSwitcherMiddleware(MiddlewareMixin): """ Dev/test only (enabled from settings/local.py, i.e. DEBUG=True). From 61af03b342c81bd42d7f93c8e498e734e75bdfdb Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 01:10:06 +0300 Subject: [PATCH 29/76] Use the merged django-meta template path --- cms_templates/djangocms_blog/base.html | 2 +- digitalglarus/templates/glarus_blog/base.html | 2 +- ungleich/templates/ungleich/djangocms_blog/base.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cms_templates/djangocms_blog/base.html b/cms_templates/djangocms_blog/base.html index bbaceb61..2d049245 100644 --- a/cms_templates/djangocms_blog/base.html +++ b/cms_templates/djangocms_blog/base.html @@ -2,7 +2,7 @@ {% block meta %} {% if meta %} - {% include "meta_mixin/meta.html" %} + {% include "meta/meta.html" %} {% endif %} {% endblock meta %} diff --git a/digitalglarus/templates/glarus_blog/base.html b/digitalglarus/templates/glarus_blog/base.html index a7ecc8a6..e5148cfe 100644 --- a/digitalglarus/templates/glarus_blog/base.html +++ b/digitalglarus/templates/glarus_blog/base.html @@ -2,7 +2,7 @@ {% block meta %} {% if meta %} -{% include "meta_mixin/meta.html" %} +{% include "meta/meta.html" %} {% endif %} {% endblock meta %} diff --git a/ungleich/templates/ungleich/djangocms_blog/base.html b/ungleich/templates/ungleich/djangocms_blog/base.html index 91b85acc..c9cf6ac9 100644 --- a/ungleich/templates/ungleich/djangocms_blog/base.html +++ b/ungleich/templates/ungleich/djangocms_blog/base.html @@ -2,7 +2,7 @@ {#{% extends CMS_TEMPLATE %}#} {% block meta %} {% if meta %} - {% include "meta_mixin/meta.html" %} + {% include "meta/meta.html" %} {% endif %} {% endblock meta %} {% block content %} From 292f3d05137e519191863fdcfdd3736acde6b05b Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 01:10:06 +0300 Subject: [PATCH 30/76] Adapt code to django CMS 3.6 and Django 1.11 Decide the home redirect per request instead of querying at import time, replace the removed _get_placeholder helper, stop shadowing the is_superuser field, and set the seeded home page as the CMS home. --- dynamicweb/urls.py | 14 +++++++++----- membership/models.py | 3 --- ungleich_page/cms_menus.py | 10 ++-------- utils/management/commands/seed_dummy_data.py | 4 +++- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index 113a5cda..b9a02981 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -41,9 +41,14 @@ urlpatterns += i18n_patterns( ) # note the django CMS URLs included via i18n_patterns -REDIRECT_TO_CMS = False -if Page.objects.filter(site_id=Site.objects.get_current().id).count(): - REDIRECT_TO_CMS = True + + +def home_view(request, *args, **kwargs): + # decided per request so that importing the urlconf needs no database + if Page.objects.filter(node__site_id=Site.objects.get_current().id).exists(): + return RedirectView.as_view(url='/cms')(request, *args, **kwargs) + return LandingView.as_view()(request, *args, **kwargs) + urlpatterns += i18n_patterns( url(r'^admin/', include(admin.site.urls)), @@ -64,8 +69,7 @@ urlpatterns += i18n_patterns( url(r'^cms/', include('cms.urls')), url(r'^blog/', include('djangocms_blog.urls', namespace='djangocms_blog')), url(r'^webhooks/', webhook_views.handle_webhook), - url(r'^$', RedirectView.as_view(url='/cms') if REDIRECT_TO_CMS - else LandingView.as_view()), + url(r'^$', home_view), url(r'^', include('ungleich_page.urls', namespace='ungleich_page')), ) diff --git a/membership/models.py b/membership/models.py index 81054fb9..abf30467 100644 --- a/membership/models.py +++ b/membership/models.py @@ -202,9 +202,6 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): def get_random_password(cls): return get_random_string(24) - def is_superuser(self): - return False - def get_full_name(self): # The user is identified by their email address return self.email diff --git a/ungleich_page/cms_menus.py b/ungleich_page/cms_menus.py index 4e17c352..25d43608 100644 --- a/ungleich_page/cms_menus.py +++ b/ungleich_page/cms_menus.py @@ -2,7 +2,6 @@ from menus.base import NavigationNode from menus.menu_pool import menu_pool from django.utils.translation import ugettext_lazy as _ from cms.menu_bases import CMSAttachMenu -from cms.templatetags.cms_tags import _get_placeholder from cms.utils.plugins import get_plugins @@ -15,9 +14,6 @@ class GlasfaserMenu(CMSAttachMenu): glasfaser_cms = 'ungleich_page/glasfaser_cms_page.html' if (request and request.current_page and request.current_page.get_template() == glasfaser_cms): - template_context = { - "request": request, - } placeholder_name_list = [ 'Top Section', 'Middle Section', 'Glasfaser Services', 'Glasfaser About', 'Contact Section' @@ -27,10 +23,8 @@ class GlasfaserMenu(CMSAttachMenu): 'UngelichTextSection', 'Service', 'About' ] for placeholder_name in placeholder_name_list: - placeholder = _get_placeholder( - request.current_page, request.current_page, - template_context, placeholder_name - ) + placeholder = request.current_page.get_placeholders().filter( + slot=placeholder_name).first() plugins = get_plugins( request, placeholder, request.current_page.get_template() ) diff --git a/utils/management/commands/seed_dummy_data.py b/utils/management/commands/seed_dummy_data.py index 8a8c562c..367f9cbc 100644 --- a/utils/management/commands/seed_dummy_data.py +++ b/utils/management/commands/seed_dummy_data.py @@ -137,7 +137,7 @@ class Command(BaseCommand): page.publish(LANGUAGE) def seed_page(self, site): - if Page.objects.filter(reverse_id=REVERSE_ID, site=site).exists(): + if Page.objects.filter(reverse_id=REVERSE_ID, node__site=site).exists(): return template = SITE_TEMPLATES.get(site.domain, TEMPLATE) name, tagline = SITE_TEXTS.get( @@ -146,6 +146,8 @@ class Command(BaseCommand): name, template, LANGUAGE, slug='home', meta_description=tagline, published=True, in_navigation=True, site=site, reverse_id=REVERSE_ID) + # django CMS >= 3.5 serves /cms/ from the page flagged as home + home.set_as_homepage() self.add_text(home, '

    {}

    {}

    '.format(name, tagline)) for slug, title, text in SUB_PAGES: child = create_page( From 3f36b0899ba8b9155b6a14b06b56ba166235298c Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 01:10:06 +0300 Subject: [PATCH 31/76] Apply legacy migration dependencies before migrating --- start-web.sh | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/start-web.sh b/start-web.sh index dc2be8d0..136c4bbd 100644 --- a/start-web.sh +++ b/start-web.sh @@ -10,7 +10,25 @@ import json, os import django django.setup() from django.core.management import call_command -# system checks import urls.py, which queries the DB, so skip them here +from django.db.migrations.loader import MigrationLoader + +# Databases created by older package versions can have a migration applied +# before the (newer) migrations it now depends on. Django refuses to migrate +# then, so apply those dependencies first with the history check switched off. +from django.db import connection +from django.db.migrations.recorder import MigrationRecorder + +recorder = MigrationRecorder(connection) +recorder.ensure_schema() +legacy = recorder.migration_qs.filter(app='cmsplugin_filer_image').exists() +check = MigrationLoader.check_consistent_history +if legacy: + MigrationLoader.check_consistent_history = lambda *args, **kwargs: None +try: + if legacy: + call_command('migrate', 'cmsplugin_filer_image', interactive=False, skip_checks=True) +finally: + MigrationLoader.check_consistent_history = check call_command('migrate', interactive=False, skip_checks=True) from django.contrib.sites.models import Site From 9c465422b63960459c4ac0bff94bd3cf88e84ece Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 10:32:20 +0300 Subject: [PATCH 32/76] Vendor stored_messages and cmsplugin_filer packages patched for Django 2 --- cmsplugin_filer_file/__init__.py | 1 + cmsplugin_filer_file/cms_plugins.py | 61 +++++ cmsplugin_filer_file/conf.py | 16 ++ cmsplugin_filer_file/forms.py | 20 ++ .../locale/ca/LC_MESSAGES/django.po | 47 ++++ .../locale/de/LC_MESSAGES/django.po | 47 ++++ .../locale/en/LC_MESSAGES/django.po | 46 ++++ .../locale/es/LC_MESSAGES/django.po | 47 ++++ .../locale/et/LC_MESSAGES/django.po | 47 ++++ .../locale/eu/LC_MESSAGES/django.po | 46 ++++ .../locale/fi/LC_MESSAGES/django.po | 47 ++++ .../locale/fr/LC_MESSAGES/django.po | 49 ++++ .../locale/it/LC_MESSAGES/django.po | 48 ++++ .../locale/it_IT/LC_MESSAGES/django.po | 46 ++++ .../locale/lt/LC_MESSAGES/django.po | 48 ++++ .../locale/lt_LT/LC_MESSAGES/django.po | 46 ++++ .../locale/nb/LC_MESSAGES/django.po | 46 ++++ .../locale/nl/LC_MESSAGES/django.po | 48 ++++ .../locale/nn/LC_MESSAGES/django.po | 46 ++++ .../locale/pl_PL/LC_MESSAGES/django.po | 48 ++++ .../locale/pt_BR/LC_MESSAGES/django.po | 47 ++++ .../locale/ru/LC_MESSAGES/django.po | 48 ++++ .../locale/zh_CN/LC_MESSAGES/django.po | 47 ++++ .../migrations/0001_initial.py | 31 +++ .../migrations/0002_auto_20160112_1617.py | 21 ++ .../0003_filerfile_link_attributes.py | 21 ++ .../migrations/0004_auto_20160705_1334.py | 21 ++ .../migrations/0005_auto_20160713_1853.py | 19 ++ cmsplugin_filer_file/migrations/__init__.py | 0 cmsplugin_filer_file/models.py | 83 +++++++ .../plugins/file/default.html | 7 + cmsplugin_filer_folder/__init__.py | 1 + cmsplugin_filer_folder/admin.py | 0 cmsplugin_filer_folder/cms_plugins.py | 75 ++++++ cmsplugin_filer_folder/conf.py | 18 ++ .../locale/ca/LC_MESSAGES/django.po | 51 ++++ .../locale/de/LC_MESSAGES/django.po | 51 ++++ .../locale/en/LC_MESSAGES/django.po | 50 ++++ .../locale/es/LC_MESSAGES/django.po | 51 ++++ .../locale/et/LC_MESSAGES/django.po | 51 ++++ .../locale/eu/LC_MESSAGES/django.po | 50 ++++ .../locale/fi/LC_MESSAGES/django.po | 51 ++++ .../locale/fr/LC_MESSAGES/django.po | 52 ++++ .../locale/it/LC_MESSAGES/django.po | 52 ++++ .../locale/it_IT/LC_MESSAGES/django.po | 50 ++++ .../locale/lt/LC_MESSAGES/django.po | 52 ++++ .../locale/lt_LT/LC_MESSAGES/django.po | 50 ++++ .../locale/nb/LC_MESSAGES/django.po | 50 ++++ .../locale/nl/LC_MESSAGES/django.po | 52 ++++ .../locale/nn/LC_MESSAGES/django.po | 50 ++++ .../locale/pl_PL/LC_MESSAGES/django.po | 51 ++++ .../locale/pt_BR/LC_MESSAGES/django.po | 51 ++++ .../locale/ru/LC_MESSAGES/django.po | 51 ++++ .../locale/zh_CN/LC_MESSAGES/django.po | 51 ++++ .../migrations/0001_initial.py | 30 +++ .../migrations/0002_auto_20160113_1318.py | 21 ++ .../migrations/0003_auto_20160713_1853.py | 19 ++ cmsplugin_filer_folder/migrations/__init__.py | 0 cmsplugin_filer_folder/models.py | 49 ++++ .../cmsplugin_filer_folder/css/slideshow.css | 2 + .../js/jquery.cycle.lite-1.7.js | 235 ++++++++++++++++++ .../plugins/folder/default.html | 55 ++++ cmsplugin_filer_image/__init__.py | 4 + cmsplugin_filer_image/apps.py | 7 + cmsplugin_filer_image/cms_plugins.py | 147 +++++++++++ cmsplugin_filer_image/conf.py | 16 ++ cmsplugin_filer_image/forms.py | 20 ++ .../integrations/__init__.py | 1 + .../integrations/ckeditor.py | 29 +++ .../locale/ca/LC_MESSAGES/django.po | 162 ++++++++++++ .../locale/de/LC_MESSAGES/django.po | 163 ++++++++++++ .../locale/en/LC_MESSAGES/django.po | 161 ++++++++++++ .../locale/es/LC_MESSAGES/django.po | 162 ++++++++++++ .../locale/et/LC_MESSAGES/django.po | 162 ++++++++++++ .../locale/eu/LC_MESSAGES/django.po | 161 ++++++++++++ .../locale/fi/LC_MESSAGES/django.po | 162 ++++++++++++ .../locale/fr/LC_MESSAGES/django.po | 165 ++++++++++++ .../locale/it/LC_MESSAGES/django.po | 164 ++++++++++++ .../locale/it_IT/LC_MESSAGES/django.po | 161 ++++++++++++ .../locale/lt/LC_MESSAGES/django.po | 163 ++++++++++++ .../locale/lt_LT/LC_MESSAGES/django.po | 161 ++++++++++++ .../locale/nb/LC_MESSAGES/django.po | 161 ++++++++++++ .../locale/nl/LC_MESSAGES/django.po | 164 ++++++++++++ .../locale/nn/LC_MESSAGES/django.po | 161 ++++++++++++ .../locale/pl_PL/LC_MESSAGES/django.po | 162 ++++++++++++ .../locale/pt_BR/LC_MESSAGES/django.po | 162 ++++++++++++ .../locale/ru/LC_MESSAGES/django.po | 163 ++++++++++++ .../locale/zh_CN/LC_MESSAGES/django.po | 162 ++++++++++++ .../migrations/0001_initial.py | 75 ++++++ .../migrations/0002_auto_20160108_1708.py | 27 ++ ...thumbnail_option_to_filer_20160119_1720.py | 62 +++++ .../migrations/0004_auto_20160120_0950.py | 22 ++ .../migrations/0005_auto_20160224_1457.py | 19 ++ .../migrations/0006_auto_20160427_1438.py | 19 ++ .../0007_filerimage_link_attributes.py | 20 ++ .../migrations/0008_auto_20160705_1334.py | 21 ++ .../migrations/0009_auto_20160713_1853.py | 19 ++ cmsplugin_filer_image/migrations/__init__.py | 0 cmsplugin_filer_image/models.py | 134 ++++++++++ .../plugins/image/default.html | 35 +++ cmsplugin_filer_link/__init__.py | 1 + cmsplugin_filer_link/cms_plugins.py | 67 +++++ cmsplugin_filer_link/forms.py | 20 ++ .../locale/ca/LC_MESSAGES/django.po | 67 +++++ .../locale/de/LC_MESSAGES/django.po | 68 +++++ .../locale/en/LC_MESSAGES/django.po | 66 +++++ .../locale/es/LC_MESSAGES/django.po | 67 +++++ .../locale/et/LC_MESSAGES/django.po | 67 +++++ .../locale/eu/LC_MESSAGES/django.po | 66 +++++ .../locale/fi/LC_MESSAGES/django.po | 66 +++++ .../locale/fr/LC_MESSAGES/django.po | 68 +++++ .../locale/it/LC_MESSAGES/django.po | 67 +++++ .../locale/it_IT/LC_MESSAGES/django.po | 66 +++++ .../locale/lt/LC_MESSAGES/django.po | 68 +++++ .../locale/lt_LT/LC_MESSAGES/django.po | 66 +++++ .../locale/nb/LC_MESSAGES/django.po | 66 +++++ .../locale/nl/LC_MESSAGES/django.po | 69 +++++ .../locale/nn/LC_MESSAGES/django.po | 66 +++++ .../locale/pl_PL/LC_MESSAGES/django.po | 68 +++++ .../locale/pt_BR/LC_MESSAGES/django.po | 67 +++++ .../locale/ru/LC_MESSAGES/django.po | 67 +++++ .../locale/zh_CN/LC_MESSAGES/django.po | 67 +++++ .../migrations/0001_initial.py | 35 +++ .../migrations/0002_auto_20160108_1710.py | 27 ++ .../migrations/0003_auto_20160224_1453.py | 19 ++ .../migrations/0004_auto_20160224_1457.py | 19 ++ .../0005_filerlinkplugin_link_attributes.py | 20 ++ .../migrations/0006_auto_20160705_1334.py | 21 ++ .../migrations/0007_auto_20160713_1853.py | 19 ++ cmsplugin_filer_link/migrations/__init__.py | 0 cmsplugin_filer_link/models.py | 52 ++++ .../templates/cmsplugin_filer_link/link.html | 1 + cmsplugin_filer_utils/__init__.py | 22 ++ cmsplugin_filer_utils/migration.py | 26 ++ cmsplugin_filer_video/__init__.py | 1 + cmsplugin_filer_video/cms_plugins.py | 62 +++++ cmsplugin_filer_video/forms.py | 9 + .../locale/ca/LC_MESSAGES/django.po | 120 +++++++++ .../locale/de/LC_MESSAGES/django.po | 120 +++++++++ .../locale/en/LC_MESSAGES/django.po | 119 +++++++++ .../locale/es/LC_MESSAGES/django.po | 121 +++++++++ .../locale/et/LC_MESSAGES/django.po | 120 +++++++++ .../locale/eu/LC_MESSAGES/django.po | 119 +++++++++ .../locale/fi/LC_MESSAGES/django.po | 120 +++++++++ .../locale/fr/LC_MESSAGES/django.po | 121 +++++++++ .../locale/it/LC_MESSAGES/django.po | 122 +++++++++ .../locale/it_IT/LC_MESSAGES/django.po | 119 +++++++++ .../locale/lt/LC_MESSAGES/django.po | 120 +++++++++ .../locale/lt_LT/LC_MESSAGES/django.po | 119 +++++++++ .../locale/nb/LC_MESSAGES/django.po | 119 +++++++++ .../locale/nl/LC_MESSAGES/django.po | 121 +++++++++ .../locale/nn/LC_MESSAGES/django.po | 119 +++++++++ .../locale/pl_PL/LC_MESSAGES/django.po | 120 +++++++++ .../locale/pt_BR/LC_MESSAGES/django.po | 120 +++++++++ .../locale/ru/LC_MESSAGES/django.po | 120 +++++++++ .../locale/zh_CN/LC_MESSAGES/django.po | 120 +++++++++ .../migrations/0001_initial.py | 45 ++++ .../migrations/0002_auto_20160108_1710.py | 27 ++ .../migrations/0003_auto_20160713_1853.py | 19 ++ cmsplugin_filer_video/migrations/__init__.py | 0 cmsplugin_filer_video/models.py | 72 ++++++ cmsplugin_filer_video/settings.py | 20 ++ .../cmsplugin_filer_video/video.html | 46 ++++ .../templatetags/__init__.py | 0 .../templatetags/cmsplugin_filer_js_tmp.py | 14 ++ stored_messages/LICENSE | 12 + stored_messages/__init__.py | 11 + stored_messages/admin.py | 7 + stored_messages/api.py | 69 +++++ stored_messages/backends/__init__.py | 3 + stored_messages/backends/base.py | 159 ++++++++++++ stored_messages/backends/default/__init__.py | 2 + stored_messages/backends/default/backend.py | 81 ++++++ stored_messages/backends/exceptions.py | 8 + stored_messages/backends/redis/__init__.py | 2 + stored_messages/backends/redis/backend.py | 143 +++++++++++ stored_messages/backends/signals.py | 7 + stored_messages/compat.py | 6 + stored_messages/constants.py | 16 ++ stored_messages/migrations/0001_initial.py | 54 ++++ .../migrations/0002_message_url.py | 19 ++ stored_messages/migrations/__init__.py | 0 stored_messages/models.py | 58 +++++ stored_messages/serializers.py | 10 + stored_messages/settings.py | 130 ++++++++++ stored_messages/static/js/stored_messages.js | 0 stored_messages/storage.py | 103 ++++++++ .../templates/stored_messages/base.html | 21 ++ .../stored_messages/stored_messages_list.html | 7 + stored_messages/templatetags/__init__.py | 0 .../templatetags/stored_messages_tags.py | 49 ++++ stored_messages/urls.py | 17 ++ stored_messages/views.py | 60 +++++ 193 files changed, 11695 insertions(+) create mode 100644 cmsplugin_filer_file/__init__.py create mode 100644 cmsplugin_filer_file/cms_plugins.py create mode 100644 cmsplugin_filer_file/conf.py create mode 100644 cmsplugin_filer_file/forms.py create mode 100644 cmsplugin_filer_file/locale/ca/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/de/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/en/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/es/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/et/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/eu/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/fi/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/fr/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/it/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/it_IT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/lt/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/lt_LT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/nb/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/nl/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/nn/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/pl_PL/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/ru/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_file/migrations/0001_initial.py create mode 100644 cmsplugin_filer_file/migrations/0002_auto_20160112_1617.py create mode 100644 cmsplugin_filer_file/migrations/0003_filerfile_link_attributes.py create mode 100644 cmsplugin_filer_file/migrations/0004_auto_20160705_1334.py create mode 100644 cmsplugin_filer_file/migrations/0005_auto_20160713_1853.py create mode 100644 cmsplugin_filer_file/migrations/__init__.py create mode 100644 cmsplugin_filer_file/models.py create mode 100644 cmsplugin_filer_file/templates/cmsplugin_filer_file/plugins/file/default.html create mode 100644 cmsplugin_filer_folder/__init__.py create mode 100644 cmsplugin_filer_folder/admin.py create mode 100644 cmsplugin_filer_folder/cms_plugins.py create mode 100644 cmsplugin_filer_folder/conf.py create mode 100644 cmsplugin_filer_folder/locale/ca/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/de/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/en/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/es/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/et/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/eu/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/fi/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/fr/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/it/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/it_IT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/lt/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/lt_LT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/nb/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/nl/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/nn/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/pl_PL/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/ru/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_folder/migrations/0001_initial.py create mode 100644 cmsplugin_filer_folder/migrations/0002_auto_20160113_1318.py create mode 100644 cmsplugin_filer_folder/migrations/0003_auto_20160713_1853.py create mode 100644 cmsplugin_filer_folder/migrations/__init__.py create mode 100644 cmsplugin_filer_folder/models.py create mode 100644 cmsplugin_filer_folder/static/cmsplugin_filer_folder/css/slideshow.css create mode 100644 cmsplugin_filer_folder/static/cmsplugin_filer_folder/js/jquery.cycle.lite-1.7.js create mode 100644 cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html create mode 100644 cmsplugin_filer_image/__init__.py create mode 100644 cmsplugin_filer_image/apps.py create mode 100644 cmsplugin_filer_image/cms_plugins.py create mode 100644 cmsplugin_filer_image/conf.py create mode 100644 cmsplugin_filer_image/forms.py create mode 100644 cmsplugin_filer_image/integrations/__init__.py create mode 100644 cmsplugin_filer_image/integrations/ckeditor.py create mode 100644 cmsplugin_filer_image/locale/ca/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/de/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/en/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/es/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/et/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/eu/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/fi/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/fr/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/it/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/it_IT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/lt/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/lt_LT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/nb/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/nl/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/nn/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/pl_PL/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/ru/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_image/migrations/0001_initial.py create mode 100644 cmsplugin_filer_image/migrations/0002_auto_20160108_1708.py create mode 100644 cmsplugin_filer_image/migrations/0003_mv_thumbnail_option_to_filer_20160119_1720.py create mode 100644 cmsplugin_filer_image/migrations/0004_auto_20160120_0950.py create mode 100644 cmsplugin_filer_image/migrations/0005_auto_20160224_1457.py create mode 100644 cmsplugin_filer_image/migrations/0006_auto_20160427_1438.py create mode 100644 cmsplugin_filer_image/migrations/0007_filerimage_link_attributes.py create mode 100644 cmsplugin_filer_image/migrations/0008_auto_20160705_1334.py create mode 100644 cmsplugin_filer_image/migrations/0009_auto_20160713_1853.py create mode 100644 cmsplugin_filer_image/migrations/__init__.py create mode 100644 cmsplugin_filer_image/models.py create mode 100644 cmsplugin_filer_image/templates/cmsplugin_filer_image/plugins/image/default.html create mode 100644 cmsplugin_filer_link/__init__.py create mode 100644 cmsplugin_filer_link/cms_plugins.py create mode 100644 cmsplugin_filer_link/forms.py create mode 100644 cmsplugin_filer_link/locale/ca/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/de/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/en/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/es/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/et/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/eu/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/fi/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/fr/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/it/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/it_IT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/lt/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/lt_LT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/nb/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/nl/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/nn/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/pl_PL/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/ru/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_link/migrations/0001_initial.py create mode 100644 cmsplugin_filer_link/migrations/0002_auto_20160108_1710.py create mode 100644 cmsplugin_filer_link/migrations/0003_auto_20160224_1453.py create mode 100644 cmsplugin_filer_link/migrations/0004_auto_20160224_1457.py create mode 100644 cmsplugin_filer_link/migrations/0005_filerlinkplugin_link_attributes.py create mode 100644 cmsplugin_filer_link/migrations/0006_auto_20160705_1334.py create mode 100644 cmsplugin_filer_link/migrations/0007_auto_20160713_1853.py create mode 100644 cmsplugin_filer_link/migrations/__init__.py create mode 100644 cmsplugin_filer_link/models.py create mode 100644 cmsplugin_filer_link/templates/cmsplugin_filer_link/link.html create mode 100644 cmsplugin_filer_utils/__init__.py create mode 100644 cmsplugin_filer_utils/migration.py create mode 100644 cmsplugin_filer_video/__init__.py create mode 100644 cmsplugin_filer_video/cms_plugins.py create mode 100644 cmsplugin_filer_video/forms.py create mode 100644 cmsplugin_filer_video/locale/ca/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/de/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/en/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/es/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/et/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/eu/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/fi/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/fr/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/it/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/it_IT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/lt/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/lt_LT/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/nb/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/nl/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/nn/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/pl_PL/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/pt_BR/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/ru/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/locale/zh_CN/LC_MESSAGES/django.po create mode 100644 cmsplugin_filer_video/migrations/0001_initial.py create mode 100644 cmsplugin_filer_video/migrations/0002_auto_20160108_1710.py create mode 100644 cmsplugin_filer_video/migrations/0003_auto_20160713_1853.py create mode 100644 cmsplugin_filer_video/migrations/__init__.py create mode 100644 cmsplugin_filer_video/models.py create mode 100644 cmsplugin_filer_video/settings.py create mode 100644 cmsplugin_filer_video/templates/cmsplugin_filer_video/video.html create mode 100644 cmsplugin_filer_video/templatetags/__init__.py create mode 100644 cmsplugin_filer_video/templatetags/cmsplugin_filer_js_tmp.py create mode 100644 stored_messages/LICENSE create mode 100644 stored_messages/__init__.py create mode 100644 stored_messages/admin.py create mode 100644 stored_messages/api.py create mode 100644 stored_messages/backends/__init__.py create mode 100644 stored_messages/backends/base.py create mode 100644 stored_messages/backends/default/__init__.py create mode 100644 stored_messages/backends/default/backend.py create mode 100644 stored_messages/backends/exceptions.py create mode 100644 stored_messages/backends/redis/__init__.py create mode 100644 stored_messages/backends/redis/backend.py create mode 100644 stored_messages/backends/signals.py create mode 100644 stored_messages/compat.py create mode 100644 stored_messages/constants.py create mode 100644 stored_messages/migrations/0001_initial.py create mode 100644 stored_messages/migrations/0002_message_url.py create mode 100644 stored_messages/migrations/__init__.py create mode 100644 stored_messages/models.py create mode 100644 stored_messages/serializers.py create mode 100644 stored_messages/settings.py create mode 100644 stored_messages/static/js/stored_messages.js create mode 100644 stored_messages/storage.py create mode 100644 stored_messages/templates/stored_messages/base.html create mode 100644 stored_messages/templates/stored_messages/stored_messages_list.html create mode 100644 stored_messages/templatetags/__init__.py create mode 100644 stored_messages/templatetags/stored_messages_tags.py create mode 100644 stored_messages/urls.py create mode 100644 stored_messages/views.py diff --git a/cmsplugin_filer_file/__init__.py b/cmsplugin_filer_file/__init__.py new file mode 100644 index 00000000..0b2f79db --- /dev/null +++ b/cmsplugin_filer_file/__init__.py @@ -0,0 +1 @@ +__version__ = "1.1.3" diff --git a/cmsplugin_filer_file/cms_plugins.py b/cmsplugin_filer_file/cms_plugins.py new file mode 100644 index 00000000..44270bfe --- /dev/null +++ b/cmsplugin_filer_file/cms_plugins.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.template.loader import select_template +from django.templatetags.static import static +from django.utils.translation import ugettext_lazy as _ + +from cms.plugin_pool import plugin_pool +from cms.plugin_base import CMSPluginBase + +from .conf import settings +from .forms import FilerFileForm +from .models import FilerFile + + +class FilerFilePlugin(CMSPluginBase): + form = FilerFileForm + module = 'Filer' + model = FilerFile + name = _("File") + TEMPLATE_NAME = 'cmsplugin_filer_file/plugins/file/%s.html' + render_template = TEMPLATE_NAME % 'default' + text_enabled = True + + fieldsets = ( + (None, { + 'fields': [ + 'title', 'file', 'target_blank' + ] + }), + (_('Advanced'), { + 'classes': ['collapse', ], + 'fields': [ + 'link_attributes', + ] + }) + ) + + if settings.CMSPLUGIN_FILER_FILE_STYLE_CHOICES: + fieldsets[0][1]['fields'].append('style') + + def render(self, context, instance, placeholder): + context['object'] = instance + return super(FilerFilePlugin, self).render(context, instance, placeholder) + + def get_render_template(self, context, instance, placeholder): + template = select_template(( + 'cmsplugin_filer_file/plugins/file.html', # backwards compatibility. deprecated! + self.TEMPLATE_NAME % instance.style, + self.TEMPLATE_NAME % 'default', + )) + return template + + def icon_src(self, instance): + file_icon = instance.get_icon_url() + + if file_icon: + return file_icon + return static("filer/icons/file_%sx%s.png" % (32, 32,)) + +plugin_pool.register_plugin(FilerFilePlugin) diff --git a/cmsplugin_filer_file/conf.py b/cmsplugin_filer_file/conf.py new file mode 100644 index 00000000..c6540397 --- /dev/null +++ b/cmsplugin_filer_file/conf.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +from django.conf import settings # NOQA +from appconf import AppConf + + +class CmspluginFilerFolderAppConf(AppConf): + STYLE_CHOICES = ( + # ('default', _('Default')), # define your styles here. + ) + DEFAULT_STYLE = '' + + def configure(self): + # set DEFAULT_STYLE to '' if it is not in STYLE_CHOICES + if not self.configured_data['DEFAULT_STYLE'] in [s for s, l in self.configured_data['STYLE_CHOICES']]: + self.configured_data['DEFAULT_STYLE'] = '' + return self.configured_data diff --git a/cmsplugin_filer_file/forms.py b/cmsplugin_filer_file/forms.py new file mode 100644 index 00000000..307f6ea8 --- /dev/null +++ b/cmsplugin_filer_file/forms.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django import forms + +from djangocms_attributes_field.widgets import AttributesWidget + +from .models import FilerFile + + +class FilerFileForm(forms.ModelForm): + + class Meta: + model = FilerFile + exclude = [] + + def __init__(self, *args, **kwargs): + super(FilerFileForm, self).__init__(*args, **kwargs) + self.fields['link_attributes'].widget = AttributesWidget() diff --git a/cmsplugin_filer_file/locale/ca/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/ca/LC_MESSAGES/django.po new file mode 100644 index 00000000..f802b1e1 --- /dev/null +++ b/cmsplugin_filer_file/locale/ca/LC_MESSAGES/django.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Roger Pons , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Catalan (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Fitxer" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "títol" + +#: models.py:36 +msgid "file" +msgstr "fitxer" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Obre l'enllaç en una nova finestra" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/de/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/de/LC_MESSAGES/django.po new file mode 100644 index 00000000..b092420f --- /dev/null +++ b/cmsplugin_filer_file/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Jannik V. , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: German (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Datei" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "Titel" + +#: models.py:36 +msgid "file" +msgstr "Datei" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Link in neuem Fenster öffnen" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/en/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..9dfb2f36 --- /dev/null +++ b/cmsplugin_filer_file/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "" + +#: models.py:36 +msgid "file" +msgstr "" + +#: models.py:40 +msgid "Open link in new window" +msgstr "" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/es/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..0640879c --- /dev/null +++ b/cmsplugin_filer_file/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Javier Sánchez , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Spanish (http://www.transifex.com/divio/cmsplugin-filer/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Archivo" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "título" + +#: models.py:36 +msgid "file" +msgstr "archivo" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Abrir el enlace en una nueva ventana" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/et/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/et/LC_MESSAGES/django.po new file mode 100644 index 00000000..0b386bb3 --- /dev/null +++ b/cmsplugin_filer_file/locale/et/LC_MESSAGES/django.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rivo Zängov , 2012-2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Estonian (http://www.transifex.com/divio/cmsplugin-filer/language/et/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Fail" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "pealkiri" + +#: models.py:36 +msgid "file" +msgstr "fail" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Ava link uues aknas" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/eu/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/eu/LC_MESSAGES/django.po new file mode 100644 index 00000000..910468a3 --- /dev/null +++ b/cmsplugin_filer_file/locale/eu/LC_MESSAGES/django.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Basque (http://www.transifex.com/divio/cmsplugin-filer/language/eu/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: eu\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Fitxategia" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "izenburua" + +#: models.py:36 +msgid "file" +msgstr "fitxategia" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Ireki esteka leiho berrian" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/fi/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/fi/LC_MESSAGES/django.po new file mode 100644 index 00000000..9c4592f2 --- /dev/null +++ b/cmsplugin_filer_file/locale/fi/LC_MESSAGES/django.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Klaus Dahlén , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Finnish (http://www.transifex.com/divio/cmsplugin-filer/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Tiedosto" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "otsikko" + +#: models.py:36 +msgid "file" +msgstr "" + +#: models.py:40 +msgid "Open link in new window" +msgstr "" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/fr/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..4af66286 --- /dev/null +++ b/cmsplugin_filer_file/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,49 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Bertrand Bordage , 2012 +# Frigory33 , 2016 +# Dylann CORDEL , 2014 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-06 18:33+0000\n" +"Last-Translator: Frigory33 \n" +"Language-Team: French (http://www.transifex.com/divio/cmsplugin-filer/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Fichier" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "Avancé" + +#: models.py:34 +msgid "title" +msgstr "titre" + +#: models.py:36 +msgid "file" +msgstr "fichier" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Ouvrir le lien dans une nouvelle fenêtre" + +#: models.py:42 +msgid "Style" +msgstr "Style" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Facultatif. Ajoute des attributs HTML au lien généré." diff --git a/cmsplugin_filer_file/locale/it/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/it/LC_MESSAGES/django.po new file mode 100644 index 00000000..ec7c2b5a --- /dev/null +++ b/cmsplugin_filer_file/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# yakky , 2012 +# yakky , 2013,2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-13 08:28+0000\n" +"Last-Translator: yakky \n" +"Language-Team: Italian (http://www.transifex.com/divio/cmsplugin-filer/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "File" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "Avanzate" + +#: models.py:34 +msgid "title" +msgstr "titolo" + +#: models.py:36 +msgid "file" +msgstr "file" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Apri collegamento in una nuova finestra" + +#: models.py:42 +msgid "Style" +msgstr "Stile" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Opzionale. Aggiungi attributi HTML al link" diff --git a/cmsplugin_filer_file/locale/it_IT/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/it_IT/LC_MESSAGES/django.po new file mode 100644 index 00000000..6fc74bc3 --- /dev/null +++ b/cmsplugin_filer_file/locale/it_IT/LC_MESSAGES/django.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Italian (Italy) (http://www.transifex.com/divio/cmsplugin-filer/language/it_IT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it_IT\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "" + +#: models.py:36 +msgid "file" +msgstr "" + +#: models.py:40 +msgid "Open link in new window" +msgstr "" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/lt/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/lt/LC_MESSAGES/django.po new file mode 100644 index 00000000..b5d4cfaf --- /dev/null +++ b/cmsplugin_filer_file/locale/lt/LC_MESSAGES/django.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Matas Dailyda, 2015 +# Matas Dailyda , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-18 09:19+0000\n" +"Last-Translator: Matas Dailyda \n" +"Language-Team: Lithuanian (http://www.transifex.com/divio/cmsplugin-filer/language/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Byla" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "Išplėstiniai" + +#: models.py:34 +msgid "title" +msgstr "pavadinimas" + +#: models.py:36 +msgid "file" +msgstr "byla" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Atverti nuorodą naujame lange" + +#: models.py:42 +msgid "Style" +msgstr "Stilius" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Nebūtina. Prideda HTML atributus prie atvaizduojamos nuorodos." diff --git a/cmsplugin_filer_file/locale/lt_LT/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/lt_LT/LC_MESSAGES/django.po new file mode 100644 index 00000000..2fcb0cc4 --- /dev/null +++ b/cmsplugin_filer_file/locale/lt_LT/LC_MESSAGES/django.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Lithuanian (Lithuania) (http://www.transifex.com/divio/cmsplugin-filer/language/lt_LT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt_LT\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "" + +#: models.py:36 +msgid "file" +msgstr "" + +#: models.py:40 +msgid "Open link in new window" +msgstr "" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/nb/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/nb/LC_MESSAGES/django.po new file mode 100644 index 00000000..d2a9dcc0 --- /dev/null +++ b/cmsplugin_filer_file/locale/nb/LC_MESSAGES/django.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Bokmål (http://www.transifex.com/divio/cmsplugin-filer/language/nb/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nb\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "" + +#: models.py:36 +msgid "file" +msgstr "" + +#: models.py:40 +msgid "Open link in new window" +msgstr "" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/nl/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/nl/LC_MESSAGES/django.po new file mode 100644 index 00000000..1fdc5e36 --- /dev/null +++ b/cmsplugin_filer_file/locale/nl/LC_MESSAGES/django.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Maikel Wever, 2013 +# kurdyma , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-27 20:23+0000\n" +"Last-Translator: kurdyma \n" +"Language-Team: Dutch (http://www.transifex.com/divio/cmsplugin-filer/language/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Bestand" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "Geavanveerd" + +#: models.py:34 +msgid "title" +msgstr "titel" + +#: models.py:36 +msgid "file" +msgstr "bestand" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Open link in nieuw venster" + +#: models.py:42 +msgid "Style" +msgstr "Stijl" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Optioneel. Voegt HTML attributen toe aan de gerenderde link." diff --git a/cmsplugin_filer_file/locale/nn/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/nn/LC_MESSAGES/django.po new file mode 100644 index 00000000..f7c29b15 --- /dev/null +++ b/cmsplugin_filer_file/locale/nn/LC_MESSAGES/django.po @@ -0,0 +1,46 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Nynorsk (http://www.transifex.com/divio/cmsplugin-filer/language/nn/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nn\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "" + +#: models.py:36 +msgid "file" +msgstr "" + +#: models.py:40 +msgid "Open link in new window" +msgstr "" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/pl_PL/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/pl_PL/LC_MESSAGES/django.po new file mode 100644 index 00000000..3e9f0a48 --- /dev/null +++ b/cmsplugin_filer_file/locale/pl_PL/LC_MESSAGES/django.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Mateusz Marzantowicz , 2013 +# Michal Bielicki , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/divio/cmsplugin-filer/language/pl_PL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Plik" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "tytuł" + +#: models.py:36 +msgid "file" +msgstr "plik" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Otwórz odnośnik w nowym oknie" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/pt_BR/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 00000000..ace78165 --- /dev/null +++ b/cmsplugin_filer_file/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rodrigo , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/divio/cmsplugin-filer/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Arquivo" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "título" + +#: models.py:36 +msgid "file" +msgstr "arquivo" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Abrir link em uma nova janela" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/ru/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 00000000..a4089c8d --- /dev/null +++ b/cmsplugin_filer_file/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,48 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Pavel , 2012 +# Pavel , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Russian (http://www.transifex.com/divio/cmsplugin-filer/language/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "Файл" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "заголовок" + +#: models.py:36 +msgid "file" +msgstr "файл" + +#: models.py:40 +msgid "Open link in new window" +msgstr "Открыть ссылку в новом окне" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/locale/zh_CN/LC_MESSAGES/django.po b/cmsplugin_filer_file/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 00000000..e8afd841 --- /dev/null +++ b/cmsplugin_filer_file/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,47 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Calvin-He , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Chinese (China) (http://www.transifex.com/divio/cmsplugin-filer/language/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: cms_plugins.py:20 +msgid "File" +msgstr "文件" + +#: cms_plugins.py:31 +msgid "Advanced" +msgstr "" + +#: models.py:34 +msgid "title" +msgstr "标题" + +#: models.py:36 +msgid "file" +msgstr "文件" + +#: models.py:40 +msgid "Open link in new window" +msgstr "在新窗口打开链接" + +#: models.py:42 +msgid "Style" +msgstr "" + +#: models.py:44 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_file/migrations/0001_initial.py b/cmsplugin_filer_file/migrations/0001_initial.py new file mode 100644 index 00000000..77b741b6 --- /dev/null +++ b/cmsplugin_filer_file/migrations/0001_initial.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings +import filer.fields.file + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0003_auto_20140926_2347'), + ('filer', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='FilerFile', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE)), + ('title', models.CharField(max_length=255, null=True, verbose_name='title', blank=True)), + ('target_blank', models.BooleanField(default=False, verbose_name='Open link in new window')), + ('style', models.CharField(default=settings.CMSPLUGIN_FILER_FILE_DEFAULT_STYLE, choices=settings.CMSPLUGIN_FILER_FILE_STYLE_CHOICES, verbose_name='Style', blank=True, max_length=255)), + ('file', filer.fields.file.FilerFileField(verbose_name='file', to='filer.File', on_delete=models.CASCADE)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/cmsplugin_filer_file/migrations/0002_auto_20160112_1617.py b/cmsplugin_filer_file/migrations/0002_auto_20160112_1617.py new file mode 100644 index 00000000..a3dd9ce8 --- /dev/null +++ b/cmsplugin_filer_file/migrations/0002_auto_20160112_1617.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import filer.fields.file +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_file', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='filerfile', + name='file', + field=filer.fields.file.FilerFileField(on_delete=django.db.models.deletion.SET_NULL, verbose_name='file', to='filer.File', null=True), + ), + ] diff --git a/cmsplugin_filer_file/migrations/0003_filerfile_link_attributes.py b/cmsplugin_filer_file/migrations/0003_filerfile_link_attributes.py new file mode 100644 index 00000000..7a2ed3f0 --- /dev/null +++ b/cmsplugin_filer_file/migrations/0003_filerfile_link_attributes.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2016-06-16 21:56 +from __future__ import unicode_literals + +from django.db import migrations +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_file', '0002_auto_20160112_1617'), + ] + + operations = [ + migrations.AddField( + model_name='filerfile', + name='link_attributes', + field=djangocms_attributes_field.fields.AttributesField(default=dict, help_text='Optional. Adds HTML attributes to the rendered link.'), + ), + ] diff --git a/cmsplugin_filer_file/migrations/0004_auto_20160705_1334.py b/cmsplugin_filer_file/migrations/0004_auto_20160705_1334.py new file mode 100644 index 00000000..748c256b --- /dev/null +++ b/cmsplugin_filer_file/migrations/0004_auto_20160705_1334.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-05 17:34 +from __future__ import unicode_literals + +from django.db import migrations +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_file', '0003_filerfile_link_attributes'), + ] + + operations = [ + migrations.AlterField( + model_name='filerfile', + name='link_attributes', + field=djangocms_attributes_field.fields.AttributesField(blank=True, default=dict, help_text='Optional. Adds HTML attributes to the rendered link.'), + ), + ] diff --git a/cmsplugin_filer_file/migrations/0005_auto_20160713_1853.py b/cmsplugin_filer_file/migrations/0005_auto_20160713_1853.py new file mode 100644 index 00000000..2d6f5afa --- /dev/null +++ b/cmsplugin_filer_file/migrations/0005_auto_20160713_1853.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_file', '0004_auto_20160705_1334'), + ] + + operations = [ + migrations.AlterField( + model_name='filerfile', + name='cmsplugin_ptr', + field=models.OneToOneField(parent_link=True, related_name='cmsplugin_filer_file_filerfile', primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE), + ), + ] diff --git a/cmsplugin_filer_file/migrations/__init__.py b/cmsplugin_filer_file/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cmsplugin_filer_file/models.py b/cmsplugin_filer_file/models.py new file mode 100644 index 00000000..9d2159c9 --- /dev/null +++ b/cmsplugin_filer_file/models.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from cms.models import CMSPlugin + +from filer.fields.file import FilerFileField + +from cmsplugin_filer_utils import FilerPluginManager +from djangocms_attributes_field.fields import AttributesField + +from .conf import settings + + +class FilerFile(CMSPlugin): + """ + Plugin for storing any type of file. + + Default template displays download link with icon (if available) and file size. + + This could be updated to use the mimetypes library to determine the type of file rather than + storing a separate icon for each different extension. + + The icon search is currently performed within get_icon_url; this is probably a performance concern. + """ + STYLE_CHOICES = settings.CMSPLUGIN_FILER_FILE_STYLE_CHOICES + DEFAULT_STYLE = settings.CMSPLUGIN_FILER_FILE_DEFAULT_STYLE + EXCLUDED_KEYS = ['href', 'target', ] + + title = models.CharField(_("title"), max_length=255, null=True, blank=True) + file = FilerFileField( + verbose_name=_('file'), + null=True, + on_delete=models.SET_NULL, + ) + target_blank = models.BooleanField(_('Open link in new window'), default=False) + style = models.CharField( + _('Style'), choices=STYLE_CHOICES, default=DEFAULT_STYLE, max_length=255, blank=True) + link_attributes = AttributesField(excluded_keys=EXCLUDED_KEYS, blank=True, + help_text=_('Optional. Adds HTML attributes to the rendered link.')) + cmsplugin_ptr = models.OneToOneField( + to=CMSPlugin, + related_name='%(app_label)s_%(class)s', + parent_link=True, + on_delete=models.CASCADE + ) + + objects = FilerPluginManager(select_related=('file',)) + + def get_icon_url(self): + if self.file_id: + return self.file.icons['32'] + return '' + + def file_exists(self): + if self.file_id: + return self.file.file.storage.exists(self.file.file.name) + return False + + def get_file_name(self): + if not self.file_id: + return '' + + if self.file.name in ('', None): + name = "%s" % (self.file.original_filename,) + else: + name = "%s" % (self.file.name,) + return name + + def get_ext(self): + return self.file.extension + + def __str__(self): + if self.title: + return self.title + elif self.file_id: + # added if, because it raised attribute error when file wasnt defined + return self.get_file_name() + return "" + + search_fields = ('title',) diff --git a/cmsplugin_filer_file/templates/cmsplugin_filer_file/plugins/file/default.html b/cmsplugin_filer_file/templates/cmsplugin_filer_file/plugins/file/default.html new file mode 100644 index 00000000..3e752753 --- /dev/null +++ b/cmsplugin_filer_file/templates/cmsplugin_filer_file/plugins/file/default.html @@ -0,0 +1,7 @@ +{% if object.file.url %} + +
    +{% if object.get_icon_url %}Icon{% endif %} +{% if object.title %}{{ object.title }}{% else %}{{ object.get_file_name }}{% endif %} {% if object.file_exists %}({{ object.file.size|filesizeformat }}){% else %}(file missing!){% endif %} + +{% endif %} diff --git a/cmsplugin_filer_folder/__init__.py b/cmsplugin_filer_folder/__init__.py new file mode 100644 index 00000000..10a7a433 --- /dev/null +++ b/cmsplugin_filer_folder/__init__.py @@ -0,0 +1 @@ +from cmsplugin_filer_file import __version__ # NOQA diff --git a/cmsplugin_filer_folder/admin.py b/cmsplugin_filer_folder/admin.py new file mode 100644 index 00000000..e69de29b diff --git a/cmsplugin_filer_folder/cms_plugins.py b/cmsplugin_filer_folder/cms_plugins.py new file mode 100644 index 00000000..36bd802f --- /dev/null +++ b/cmsplugin_filer_folder/cms_plugins.py @@ -0,0 +1,75 @@ +from cms.plugin_pool import plugin_pool +from cms.plugin_base import CMSPluginBase +from django.template.loader import select_template +from django.utils.translation import ugettext_lazy as _ +from . import models +from .conf import settings + +from filer.models.filemodels import File +from filer.models.foldermodels import Folder +from filer.models.abstract import BaseImage + + +class FilerFolderPlugin(CMSPluginBase): + module = 'Filer' + model = models.FilerFolder + name = _("Folder") + TEMPLATE_NAME = 'cmsplugin_filer_folder/plugins/folder/%s.html' + render_template = TEMPLATE_NAME % 'default' + text_enabled = False + admin_preview = False + + fieldsets = ( + (None, {'fields': ['title', 'folder']}), + ) + if settings.CMSPLUGIN_FILER_FOLDER_STYLE_CHOICES: + fieldsets[0][1]['fields'].append('style') + + def get_folder_files(self, folder, user): + qs_files = folder.files.not_instance_of(BaseImage) + if user.is_staff: + return qs_files + else: + return qs_files.filter(is_public=True) + + def get_folder_images(self, folder, user): + qs_files = folder.files.instance_of(BaseImage) + if user.is_staff: + return qs_files + else: + return qs_files.filter(is_public=True) + + def get_children(self, folder): + return folder.get_children() + + def render(self, context, instance, placeholder): + user = context['request'].user + + if instance.folder_id: + folder_files = self.get_folder_files(instance.folder, user) + folder_images = self.get_folder_images(instance.folder, user) + folder_folders = self.get_children(instance.folder) + else: + folder_files = File.objects.none() + folder_images = BaseImage.objects.none() + folder_folders = Folder.objects.none() + + context.update({ + 'object': instance, + 'folder_files': sorted(folder_files), + 'folder_images': sorted(folder_images), + 'folder_folders': folder_folders, + 'placeholder': placeholder + }) + return context + + def get_render_template(self, context, instance, placeholder): + template = select_template(( + 'cmsplugin_filer_folder/folder.html', # backwards compatibility. deprecated! + self.TEMPLATE_NAME % instance.style, + self.TEMPLATE_NAME % 'default', + )) + return template + + +plugin_pool.register_plugin(FilerFolderPlugin) diff --git a/cmsplugin_filer_folder/conf.py b/cmsplugin_filer_folder/conf.py new file mode 100644 index 00000000..c796af8f --- /dev/null +++ b/cmsplugin_filer_folder/conf.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from django.conf import settings # NOQA +from django.utils.translation import ugettext_lazy as _ +from appconf import AppConf + + +class CmspluginFilerFolderAppConf(AppConf): + STYLE_CHOICES = ( + ("list", _("List")), + ("slideshow", _("Slideshow")) + ) + DEFAULT_STYLE = 'list' + + def configure(self): + # set DEFAULT_STYLE to '' if it is not in STYLE_CHOICES + if not self.configured_data['DEFAULT_STYLE'] in [s for s, l in self.configured_data['STYLE_CHOICES']]: + self.configured_data['DEFAULT_STYLE'] = '' + return self.configured_data diff --git a/cmsplugin_filer_folder/locale/ca/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/ca/LC_MESSAGES/django.po new file mode 100644 index 00000000..cff96fa1 --- /dev/null +++ b/cmsplugin_filer_folder/locale/ca/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Roger Pons , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Catalan (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Carpeta" + +#: conf.py:9 +msgid "List" +msgstr "Llista" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Projecció de diapositives" + +#: models.py:22 +msgid "title" +msgstr "títol" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Carpetes" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Fitxers" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Imatges" diff --git a/cmsplugin_filer_folder/locale/de/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/de/LC_MESSAGES/django.po new file mode 100644 index 00000000..7464c6d9 --- /dev/null +++ b/cmsplugin_filer_folder/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Jannik V. , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: German (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Ordner" + +#: conf.py:9 +msgid "List" +msgstr "Liste" + +#: conf.py:10 +msgid "Slideshow" +msgstr "" + +#: models.py:22 +msgid "title" +msgstr "Titel" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Ordner" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Dateien" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Bilder" diff --git a/cmsplugin_filer_folder/locale/en/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..d0cc9db1 --- /dev/null +++ b/cmsplugin_filer_folder/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,50 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "" + +#: conf.py:9 +msgid "List" +msgstr "" + +#: conf.py:10 +msgid "Slideshow" +msgstr "" + +#: models.py:22 +msgid "title" +msgstr "" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "" diff --git a/cmsplugin_filer_folder/locale/es/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..38cb9b25 --- /dev/null +++ b/cmsplugin_filer_folder/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Javier Sánchez , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Spanish (http://www.transifex.com/divio/cmsplugin-filer/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Directorio" + +#: conf.py:9 +msgid "List" +msgstr "Lista" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Presentación de diapositivas" + +#: models.py:22 +msgid "title" +msgstr "título" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Directorios" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Archivos" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Imágenes" diff --git a/cmsplugin_filer_folder/locale/et/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/et/LC_MESSAGES/django.po new file mode 100644 index 00000000..c7b988f3 --- /dev/null +++ b/cmsplugin_filer_folder/locale/et/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rivo Zängov , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Estonian (http://www.transifex.com/divio/cmsplugin-filer/language/et/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Kaust" + +#: conf.py:9 +msgid "List" +msgstr "Nimekiri" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Slaidiseanss" + +#: models.py:22 +msgid "title" +msgstr "pealkiri" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Kaustad" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Failid" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Pildid" diff --git a/cmsplugin_filer_folder/locale/eu/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/eu/LC_MESSAGES/django.po new file mode 100644 index 00000000..b3ceb742 --- /dev/null +++ b/cmsplugin_filer_folder/locale/eu/LC_MESSAGES/django.po @@ -0,0 +1,50 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Basque (http://www.transifex.com/divio/cmsplugin-filer/language/eu/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: eu\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Karpeta" + +#: conf.py:9 +msgid "List" +msgstr "Zerrenda" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Diapositibak" + +#: models.py:22 +msgid "title" +msgstr "izenburua" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Karpetak" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Fitxategiak" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Irudiak" diff --git a/cmsplugin_filer_folder/locale/fi/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/fi/LC_MESSAGES/django.po new file mode 100644 index 00000000..42abcca9 --- /dev/null +++ b/cmsplugin_filer_folder/locale/fi/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Klaus Dahlén , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Finnish (http://www.transifex.com/divio/cmsplugin-filer/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Kansio" + +#: conf.py:9 +msgid "List" +msgstr "Lista" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Diaesitys" + +#: models.py:22 +msgid "title" +msgstr "otsikko" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Kansiot" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Tiedostot" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Kuvat" diff --git a/cmsplugin_filer_folder/locale/fr/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..568e1827 --- /dev/null +++ b/cmsplugin_filer_folder/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,52 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Bertrand Bordage , 2012 +# Frigory33 , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-06 18:33+0000\n" +"Last-Translator: Frigory33 \n" +"Language-Team: French (http://www.transifex.com/divio/cmsplugin-filer/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Dossier" + +#: conf.py:9 +msgid "List" +msgstr "Liste" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Diaporama" + +#: models.py:22 +msgid "title" +msgstr "titre" + +#: models.py:25 +msgid "Style" +msgstr "Style" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Dossiers" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Fichiers" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Images" diff --git a/cmsplugin_filer_folder/locale/it/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/it/LC_MESSAGES/django.po new file mode 100644 index 00000000..6a68a2dd --- /dev/null +++ b/cmsplugin_filer_folder/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,52 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# yakky , 2012 +# yakky , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-13 08:28+0000\n" +"Last-Translator: yakky \n" +"Language-Team: Italian (http://www.transifex.com/divio/cmsplugin-filer/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Cartella" + +#: conf.py:9 +msgid "List" +msgstr "Lista" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Slideshow" + +#: models.py:22 +msgid "title" +msgstr "titolo" + +#: models.py:25 +msgid "Style" +msgstr "Stile" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Cartelle" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "File" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Immagini" diff --git a/cmsplugin_filer_folder/locale/it_IT/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/it_IT/LC_MESSAGES/django.po new file mode 100644 index 00000000..9e6936b5 --- /dev/null +++ b/cmsplugin_filer_folder/locale/it_IT/LC_MESSAGES/django.po @@ -0,0 +1,50 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Italian (Italy) (http://www.transifex.com/divio/cmsplugin-filer/language/it_IT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it_IT\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "" + +#: conf.py:9 +msgid "List" +msgstr "" + +#: conf.py:10 +msgid "Slideshow" +msgstr "" + +#: models.py:22 +msgid "title" +msgstr "" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "" diff --git a/cmsplugin_filer_folder/locale/lt/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/lt/LC_MESSAGES/django.po new file mode 100644 index 00000000..1364835c --- /dev/null +++ b/cmsplugin_filer_folder/locale/lt/LC_MESSAGES/django.po @@ -0,0 +1,52 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Matas Dailyda, 2015 +# Matas Dailyda , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-18 09:19+0000\n" +"Last-Translator: Matas Dailyda \n" +"Language-Team: Lithuanian (http://www.transifex.com/divio/cmsplugin-filer/language/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Aplankas" + +#: conf.py:9 +msgid "List" +msgstr "Sąrašas" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Sąrašas su paveikslėliais" + +#: models.py:22 +msgid "title" +msgstr "pavadinimas" + +#: models.py:25 +msgid "Style" +msgstr "Stilius" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Aplankai" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Bylos" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Paveikslėliai" diff --git a/cmsplugin_filer_folder/locale/lt_LT/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/lt_LT/LC_MESSAGES/django.po new file mode 100644 index 00000000..fb1834ab --- /dev/null +++ b/cmsplugin_filer_folder/locale/lt_LT/LC_MESSAGES/django.po @@ -0,0 +1,50 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Lithuanian (Lithuania) (http://www.transifex.com/divio/cmsplugin-filer/language/lt_LT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt_LT\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "" + +#: conf.py:9 +msgid "List" +msgstr "" + +#: conf.py:10 +msgid "Slideshow" +msgstr "" + +#: models.py:22 +msgid "title" +msgstr "" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "" diff --git a/cmsplugin_filer_folder/locale/nb/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/nb/LC_MESSAGES/django.po new file mode 100644 index 00000000..054b57d2 --- /dev/null +++ b/cmsplugin_filer_folder/locale/nb/LC_MESSAGES/django.po @@ -0,0 +1,50 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Bokmål (http://www.transifex.com/divio/cmsplugin-filer/language/nb/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nb\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "" + +#: conf.py:9 +msgid "List" +msgstr "" + +#: conf.py:10 +msgid "Slideshow" +msgstr "" + +#: models.py:22 +msgid "title" +msgstr "" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "" diff --git a/cmsplugin_filer_folder/locale/nl/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/nl/LC_MESSAGES/django.po new file mode 100644 index 00000000..df931b27 --- /dev/null +++ b/cmsplugin_filer_folder/locale/nl/LC_MESSAGES/django.po @@ -0,0 +1,52 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Maikel Wever, 2013 +# kurdyma , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-27 20:24+0000\n" +"Last-Translator: kurdyma \n" +"Language-Team: Dutch (http://www.transifex.com/divio/cmsplugin-filer/language/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Map" + +#: conf.py:9 +msgid "List" +msgstr "Lijst" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Slideshow" + +#: models.py:22 +msgid "title" +msgstr "titel" + +#: models.py:25 +msgid "Style" +msgstr "Stijl" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Mappen" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Bestanden" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Afbeeldingen" diff --git a/cmsplugin_filer_folder/locale/nn/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/nn/LC_MESSAGES/django.po new file mode 100644 index 00000000..612778e0 --- /dev/null +++ b/cmsplugin_filer_folder/locale/nn/LC_MESSAGES/django.po @@ -0,0 +1,50 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Nynorsk (http://www.transifex.com/divio/cmsplugin-filer/language/nn/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nn\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "" + +#: conf.py:9 +msgid "List" +msgstr "" + +#: conf.py:10 +msgid "Slideshow" +msgstr "" + +#: models.py:22 +msgid "title" +msgstr "" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "" diff --git a/cmsplugin_filer_folder/locale/pl_PL/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/pl_PL/LC_MESSAGES/django.po new file mode 100644 index 00000000..1c05e031 --- /dev/null +++ b/cmsplugin_filer_folder/locale/pl_PL/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Mateusz Marzantowicz , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/divio/cmsplugin-filer/language/pl_PL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Katalog" + +#: conf.py:9 +msgid "List" +msgstr "Lista" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Pokaz slajdów" + +#: models.py:22 +msgid "title" +msgstr "tytuł" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Katalogi" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Pliki" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Obrazy" diff --git a/cmsplugin_filer_folder/locale/pt_BR/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 00000000..ccbb1632 --- /dev/null +++ b/cmsplugin_filer_folder/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rodrigo , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/divio/cmsplugin-filer/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Pasta" + +#: conf.py:9 +msgid "List" +msgstr "Lista" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Slideshow" + +#: models.py:22 +msgid "title" +msgstr "título" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Pastas" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Arquivos" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Imagens" diff --git a/cmsplugin_filer_folder/locale/ru/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 00000000..d46aaf9c --- /dev/null +++ b/cmsplugin_filer_folder/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Pavel , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Russian (http://www.transifex.com/divio/cmsplugin-filer/language/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "Папка" + +#: conf.py:9 +msgid "List" +msgstr "Список" + +#: conf.py:10 +msgid "Slideshow" +msgstr "Слайдшоу" + +#: models.py:22 +msgid "title" +msgstr "заголовок" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "Папки" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "Файлы" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "Изображения" diff --git a/cmsplugin_filer_folder/locale/zh_CN/LC_MESSAGES/django.po b/cmsplugin_filer_folder/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 00000000..0c090e91 --- /dev/null +++ b/cmsplugin_filer_folder/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Calvin-He , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Chinese (China) (http://www.transifex.com/divio/cmsplugin-filer/language/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: cms_plugins.py:16 +msgid "Folder" +msgstr "文件夹" + +#: conf.py:9 +msgid "List" +msgstr "列表" + +#: conf.py:10 +msgid "Slideshow" +msgstr "幻灯片" + +#: models.py:22 +msgid "title" +msgstr "标题" + +#: models.py:25 +msgid "Style" +msgstr "" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:23 +msgid "Folders" +msgstr "文件夹" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:31 +msgid "Files" +msgstr "文件" + +#: templates/cmsplugin_filer_folder/plugins/folder/default.html:39 +msgid "Images" +msgstr "图片" diff --git a/cmsplugin_filer_folder/migrations/0001_initial.py b/cmsplugin_filer_folder/migrations/0001_initial.py new file mode 100644 index 00000000..44e03b43 --- /dev/null +++ b/cmsplugin_filer_folder/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings +import filer.fields.folder + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0003_auto_20140926_2347'), + ('filer', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='FilerFolder', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE)), + ('title', models.CharField(max_length=255, null=True, verbose_name='title', blank=True)), + ('style', models.CharField(default=settings.CMSPLUGIN_FILER_FOLDER_DEFAULT_STYLE, max_length=50, verbose_name='Style', choices=settings.CMSPLUGIN_FILER_FOLDER_STYLE_CHOICES)), + ('folder', filer.fields.folder.FilerFolderField(to='filer.Folder', on_delete=models.CASCADE)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] \ No newline at end of file diff --git a/cmsplugin_filer_folder/migrations/0002_auto_20160113_1318.py b/cmsplugin_filer_folder/migrations/0002_auto_20160113_1318.py new file mode 100644 index 00000000..4d461a40 --- /dev/null +++ b/cmsplugin_filer_folder/migrations/0002_auto_20160113_1318.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import filer.fields.folder +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_folder', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='filerfolder', + name='folder', + field=filer.fields.folder.FilerFolderField(on_delete=django.db.models.deletion.SET_NULL, to='filer.Folder', null=True), + ), + ] diff --git a/cmsplugin_filer_folder/migrations/0003_auto_20160713_1853.py b/cmsplugin_filer_folder/migrations/0003_auto_20160713_1853.py new file mode 100644 index 00000000..cb7c4299 --- /dev/null +++ b/cmsplugin_filer_folder/migrations/0003_auto_20160713_1853.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_folder', '0002_auto_20160113_1318'), + ] + + operations = [ + migrations.AlterField( + model_name='filerfolder', + name='cmsplugin_ptr', + field=models.OneToOneField(parent_link=True, related_name='cmsplugin_filer_folder_filerfolder', primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE), + ), + ] diff --git a/cmsplugin_filer_folder/migrations/__init__.py b/cmsplugin_filer_folder/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cmsplugin_filer_folder/models.py b/cmsplugin_filer_folder/models.py new file mode 100644 index 00000000..5be65cf0 --- /dev/null +++ b/cmsplugin_filer_folder/models.py @@ -0,0 +1,49 @@ +from __future__ import unicode_literals + +import warnings +from django.db import models +from cms.models import CMSPlugin +from django.utils.translation import ugettext_lazy as _ +from filer.fields.folder import FilerFolderField +from .conf import settings +from cmsplugin_filer_utils import FilerPluginManager + + +class FilerFolder(CMSPlugin): + """ + Plugin for storing any type of Folder. + + Default template displays files store inside this folder. + """ + STYLE_CHOICES = settings.CMSPLUGIN_FILER_FOLDER_STYLE_CHOICES + DEFAULT_STYLE = settings.CMSPLUGIN_FILER_FOLDER_DEFAULT_STYLE + title = models.CharField(_("title"), max_length=255, null=True, blank=True) + folder = FilerFolderField(null=True, on_delete=models.SET_NULL) + style = models.CharField( + _('Style'), choices=STYLE_CHOICES, default=DEFAULT_STYLE, max_length=50) + cmsplugin_ptr = models.OneToOneField( + to=CMSPlugin, + related_name='%(app_label)s_%(class)s', + parent_link=True, + on_delete=models.CASCADE + ) + + objects = FilerPluginManager(select_related=('folder',)) + + @property + def view_option(self): + warnings.warn("view_option on cmsplugin_filer_folder.FilderFolder is deprecated. Use .style instead.", + DeprecationWarning) + return self.style + + def __str__(self): + return self.get_display_name() + + def get_display_name(self): + if self.title: + return self.title + elif self.folder_id and self.folder.name: + return self.folder.name + return "" + + search_fields = ('title',) diff --git a/cmsplugin_filer_folder/static/cmsplugin_filer_folder/css/slideshow.css b/cmsplugin_filer_folder/static/cmsplugin_filer_folder/css/slideshow.css new file mode 100644 index 00000000..f72092ae --- /dev/null +++ b/cmsplugin_filer_folder/static/cmsplugin_filer_folder/css/slideshow.css @@ -0,0 +1,2 @@ +.cmsplugin_filer_folder_slideshow { padding: 5px; } +.cmsplugin_filer_folder_slideshow img { padding: 15px; border: 1px solid #ccc; background-color: #eee; } \ No newline at end of file diff --git a/cmsplugin_filer_folder/static/cmsplugin_filer_folder/js/jquery.cycle.lite-1.7.js b/cmsplugin_filer_folder/static/cmsplugin_filer_folder/js/jquery.cycle.lite-1.7.js new file mode 100644 index 00000000..d22ed39e --- /dev/null +++ b/cmsplugin_filer_folder/static/cmsplugin_filer_folder/js/jquery.cycle.lite-1.7.js @@ -0,0 +1,235 @@ +/*! + * jQuery Cycle Lite Plugin + * http://malsup.com/jquery/cycle/lite/ + * Copyright (c) 2008-2012 M. Alsup + * Version: 1.7 (20-FEB-2013) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * Requires: jQuery v1.3.2 or later + */ +;(function($) { +"use strict"; + +var ver = 'Lite-1.7'; +var msie = /MSIE/.test(navigator.userAgent); + +$.fn.cycle = function(options) { + return this.each(function() { + options = options || {}; + + if (this.cycleTimeout) + clearTimeout(this.cycleTimeout); + + this.cycleTimeout = 0; + this.cyclePause = 0; + + var $cont = $(this); + var $slides = options.slideExpr ? $(options.slideExpr, this) : $cont.children(); + var els = $slides.get(); + if (els.length < 2) { + if (window.console) + console.log('terminating; too few slides: ' + els.length); + return; // don't bother + } + + // support metadata plugin (v1.0 and v2.0) + var opts = $.extend({}, $.fn.cycle.defaults, options || {}, $.metadata ? $cont.metadata() : $.meta ? $cont.data() : {}); + var meta = $.isFunction($cont.data) ? $cont.data(opts.metaAttr) : null; + if (meta) + opts = $.extend(opts, meta); + + opts.before = opts.before ? [opts.before] : []; + opts.after = opts.after ? [opts.after] : []; + opts.after.unshift(function(){ opts.busy=0; }); + + // allow shorthand overrides of width, height and timeout + var cls = this.className; + opts.width = parseInt((cls.match(/w:(\d+)/)||[])[1], 10) || opts.width; + opts.height = parseInt((cls.match(/h:(\d+)/)||[])[1], 10) || opts.height; + opts.timeout = parseInt((cls.match(/t:(\d+)/)||[])[1], 10) || opts.timeout; + + if ($cont.css('position') == 'static') + $cont.css('position', 'relative'); + if (opts.width) + $cont.width(opts.width); + if (opts.height && opts.height != 'auto') + $cont.height(opts.height); + + var first = 0; + $slides.css({position: 'absolute', top:0}).each(function(i) { + $(this).css('z-index', els.length-i); + }); + + $(els[first]).css('opacity',1).show(); // opacity bit needed to handle reinit case + if (msie) + els[first].style.removeAttribute('filter'); + + if (opts.fit && opts.width) + $slides.width(opts.width); + if (opts.fit && opts.height && opts.height != 'auto') + $slides.height(opts.height); + if (opts.pause) + $cont.hover(function(){this.cyclePause=1;}, function(){this.cyclePause=0;}); + + var txFn = $.fn.cycle.transitions[opts.fx]; + if (txFn) + txFn($cont, $slides, opts); + + $slides.each(function() { + var $el = $(this); + this.cycleH = (opts.fit && opts.height) ? opts.height : $el.height(); + this.cycleW = (opts.fit && opts.width) ? opts.width : $el.width(); + }); + + if (opts.cssFirst) + $($slides[first]).css(opts.cssFirst); + + if (opts.timeout) { + // ensure that timeout and speed settings are sane + if (opts.speed.constructor == String) + opts.speed = {slow: 600, fast: 200}[opts.speed] || 400; + if (!opts.sync) + opts.speed = opts.speed / 2; + while((opts.timeout - opts.speed) < 250) + opts.timeout += opts.speed; + } + opts.speedIn = opts.speed; + opts.speedOut = opts.speed; + + opts.slideCount = els.length; + opts.currSlide = first; + opts.nextSlide = 1; + + // fire artificial events + var e0 = $slides[first]; + if (opts.before.length) + opts.before[0].apply(e0, [e0, e0, opts, true]); + if (opts.after.length > 1) + opts.after[1].apply(e0, [e0, e0, opts, true]); + + if (opts.click && !opts.next) + opts.next = opts.click; + if (opts.next) + $(opts.next).unbind('click.cycle').bind('click.cycle', function(){return advance(els,opts,opts.rev?-1:1);}); + if (opts.prev) + $(opts.prev).unbind('click.cycle').bind('click.cycle', function(){return advance(els,opts,opts.rev?1:-1);}); + + if (opts.timeout) + this.cycleTimeout = setTimeout(function() { + go(els,opts,0,!opts.rev); + }, opts.timeout + (opts.delay||0)); + }); +}; + +function go(els, opts, manual, fwd) { + if (opts.busy) + return; + var p = els[0].parentNode, curr = els[opts.currSlide], next = els[opts.nextSlide]; + if (p.cycleTimeout === 0 && !manual) + return; + + if (manual || !p.cyclePause) { + if (opts.before.length) + $.each(opts.before, function(i,o) { o.apply(next, [curr, next, opts, fwd]); }); + var after = function() { + if (msie) + this.style.removeAttribute('filter'); + $.each(opts.after, function(i,o) { o.apply(next, [curr, next, opts, fwd]); }); + queueNext(opts); + }; + + if (opts.nextSlide != opts.currSlide) { + opts.busy = 1; + $.fn.cycle.custom(curr, next, opts, after); + } + var roll = (opts.nextSlide + 1) == els.length; + opts.nextSlide = roll ? 0 : opts.nextSlide+1; + opts.currSlide = roll ? els.length-1 : opts.nextSlide-1; + } else { + queueNext(opts); + } + + function queueNext(opts) { + if (opts.timeout) + p.cycleTimeout = setTimeout(function() { go(els,opts,0,!opts.rev); }, opts.timeout); + } +} + +// advance slide forward or back +function advance(els, opts, val) { + var p = els[0].parentNode, timeout = p.cycleTimeout; + if (timeout) { + clearTimeout(timeout); + p.cycleTimeout = 0; + } + opts.nextSlide = opts.currSlide + val; + if (opts.nextSlide < 0) { + opts.nextSlide = els.length - 1; + } + else if (opts.nextSlide >= els.length) { + opts.nextSlide = 0; + } + go(els, opts, 1, val>=0); + return false; +} + +$.fn.cycle.custom = function(curr, next, opts, cb) { + var $l = $(curr), $n = $(next); + $n.css(opts.cssBefore); + var fn = function() {$n.animate(opts.animIn, opts.speedIn, opts.easeIn, cb);}; + $l.animate(opts.animOut, opts.speedOut, opts.easeOut, function() { + $l.css(opts.cssAfter); + if (!opts.sync) + fn(); + }); + if (opts.sync) + fn(); +}; + +$.fn.cycle.transitions = { + fade: function($cont, $slides, opts) { + $slides.not(':eq(0)').hide(); + opts.cssBefore = { opacity: 0, display: 'block' }; + opts.cssAfter = { display: 'none' }; + opts.animOut = { opacity: 0 }; + opts.animIn = { opacity: 1 }; + }, + fadeout: function($cont, $slides, opts) { + opts.before.push(function(curr,next,opts,fwd) { + $(curr).css('zIndex',opts.slideCount + (fwd === true ? 1 : 0)); + $(next).css('zIndex',opts.slideCount + (fwd === true ? 0 : 1)); + }); + $slides.not(':eq(0)').hide(); + opts.cssBefore = { opacity: 1, display: 'block', zIndex: 1 }; + opts.cssAfter = { display: 'none', zIndex: 0 }; + opts.animOut = { opacity: 0 }; + opts.animIn = { opacity: 1 }; + } +}; + +$.fn.cycle.ver = function() { return ver; }; + +// @see: http://malsup.com/jquery/cycle/lite/ +$.fn.cycle.defaults = { + animIn: {}, + animOut: {}, + fx: 'fade', + after: null, + before: null, + cssBefore: {}, + cssAfter: {}, + delay: 0, + fit: 0, + height: 'auto', + metaAttr: 'cycle', + next: null, + pause: false, + prev: null, + speed: 1000, + slideExpr: null, + sync: true, + timeout: 4000 +}; + +})(jQuery); diff --git a/cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html b/cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html new file mode 100644 index 00000000..b43e97d8 --- /dev/null +++ b/cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html @@ -0,0 +1,55 @@ +{% load i18n thumbnail sekizai_tags staticfiles %} +{% addtoblock "css" %}{% endaddtoblock "css" %} +{% addtoblock "js" %}{% endaddtoblock "js" %} +{% addtoblock "js" %} + +{% endaddtoblock %} + + +{{ object.get_display_name }} + +{% if object.view_option == "list" %} +
    + + {% if folder_folders %} +

    {% trans "Folders" %}

    +
      + {% for folder in folder_folders %} +
    • {{ folder }}
    • + {% endfor %} +
    + {% endif %} + {% if folder_files %} +

    {% trans "Files" %}

    +
      + {% for files in folder_files %} +
    • {{ files }}
    • + {% endfor %} +
    + {% endif %} + {% if folder_images %} +

    {% trans "Images" %}

    +
      + {% for image in folder_images %} +
    • {{ image.label }}
    • + {% endfor %} +
    + {% endif %} +
    +{% else %} + +
    + {% for image in folder_images %} + + {% endfor %} +
    + +{% endif %} diff --git a/cmsplugin_filer_image/__init__.py b/cmsplugin_filer_image/__init__.py new file mode 100644 index 00000000..0a52a416 --- /dev/null +++ b/cmsplugin_filer_image/__init__.py @@ -0,0 +1,4 @@ +from cmsplugin_filer_file import __version__ # NOQA +__authors__ = ["Stefan Foulis ", ] + +default_app_config = 'cmsplugin_filer_image.apps.FilerImageConfig' diff --git a/cmsplugin_filer_image/apps.py b/cmsplugin_filer_image/apps.py new file mode 100644 index 00000000..a254c07a --- /dev/null +++ b/cmsplugin_filer_image/apps.py @@ -0,0 +1,7 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + + +class FilerImageConfig(AppConfig): + name = 'cmsplugin_filer_image' + verbose_name = _('django filer image') diff --git a/cmsplugin_filer_image/cms_plugins.py b/cmsplugin_filer_image/cms_plugins.py new file mode 100644 index 00000000..0c7673d7 --- /dev/null +++ b/cmsplugin_filer_image/cms_plugins.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.template.loader import select_template +from django.templatetags.static import static +from django.utils.translation import ugettext_lazy as _ + +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool + +from .forms import FilerImageForm +from .models import FilerImage +from .conf import settings + + +class FilerImagePlugin(CMSPluginBase): + admin_preview = False + form = FilerImageForm + model = FilerImage + module = 'Filer' + name = _("Image") + raw_id_fields = ('image', 'page_link') + text_enabled = True + + TEMPLATE_NAME = 'cmsplugin_filer_image/plugins/image/%s.html' + render_template = TEMPLATE_NAME % 'default' + + fieldsets = ( + (None, { + 'fields': [ + 'caption_text', + 'image', + 'image_url', + 'alt_text', + ] + }), + (_('Image resizing options'), { + 'fields': ( + 'use_original_image', + ('width', 'height',), + ('crop', 'upscale',), + 'thumbnail_option', + 'use_autoscale', + ) + }), + (None, { + 'fields': ('alignment',) + }), + (_('More'), { + 'classes': ('collapse',), + 'fields': ( + 'free_link', + 'page_link', + 'file_link', + ('original_link', 'target_blank',), + 'link_attributes', + 'description', + ), + }), + ) + if settings.CMSPLUGIN_FILER_IMAGE_STYLE_CHOICES: + fieldsets[0][1]['fields'].append('style') + + def _get_thumbnail_options(self, context, instance): + """ + Return the size and options of the thumbnail that should be inserted + """ + width, height = None, None + crop, upscale = False, False + subject_location = False + placeholder_width = context.get('width', None) + placeholder_height = context.get('height', None) + if instance.thumbnail_option: + # thumbnail option overrides everything else + if instance.thumbnail_option.width: + width = instance.thumbnail_option.width + if instance.thumbnail_option.height: + height = instance.thumbnail_option.height + crop = instance.thumbnail_option.crop + upscale = instance.thumbnail_option.upscale + else: + if instance.use_autoscale and placeholder_width: + # use the placeholder width as a hint for sizing + width = int(placeholder_width) + elif instance.width: + width = instance.width + if instance.use_autoscale and placeholder_height: + height = int(placeholder_height) + elif instance.height: + height = instance.height + crop = instance.crop + upscale = instance.upscale + if instance.image: + if instance.image.subject_location: + subject_location = instance.image.subject_location + if not height and width: + # height was not externally defined: use ratio to scale it by the width + height = int(float(width) * float(instance.image.height) / float(instance.image.width)) + if not width and height: + # width was not externally defined: use ratio to scale it by the height + width = int(float(height) * float(instance.image.width) / float(instance.image.height)) + if not width: + # width is still not defined. fallback the actual image width + width = instance.image.width + if not height: + # height is still not defined. fallback the actual image height + height = instance.image.height + return {'size': (width, height), + 'crop': crop, + 'upscale': upscale, + 'subject_location': subject_location} + + def get_thumbnail(self, context, instance): + if instance.image: + return instance.image.file.get_thumbnail(self._get_thumbnail_options(context, instance)) + + def render(self, context, instance, placeholder): + options = self._get_thumbnail_options(context, instance) + context.update({ + 'instance': instance, + 'link': instance.link, + 'opts': options, + 'size': options.get('size', None), + 'placeholder': placeholder + }) + return context + + def get_render_template(self, context, instance, placeholder): + template = select_template(( + 'cmsplugin_filer_image/plugins/image.html', # backwards compatibility. deprecated! + self.TEMPLATE_NAME % instance.style, + self.TEMPLATE_NAME % 'default', + )) + return template + + def icon_src(self, instance): + if instance.image: + if getattr(settings, 'FILER_IMAGE_USE_ICON', False) and '32' in instance.image.icons: + return instance.image.icons['32'] + else: + # Fake the context with a reasonable width value because it is not + # available at this stage + thumbnail = self.get_thumbnail({'width': 200}, instance) + return thumbnail.url + else: + return static("filer/icons/missingfile_%sx%s.png" % (32, 32,)) +plugin_pool.register_plugin(FilerImagePlugin) diff --git a/cmsplugin_filer_image/conf.py b/cmsplugin_filer_image/conf.py new file mode 100644 index 00000000..36da2c24 --- /dev/null +++ b/cmsplugin_filer_image/conf.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +from django.conf import settings # NOQA +from appconf import AppConf + + +class CmspluginFilerImageAppConf(AppConf): + STYLE_CHOICES = ( + # ('default', _('Default')), # define your styles here. + ) + DEFAULT_STYLE = '' + + def configure(self): + # set DEFAULT_STYLE to '' if it is not in STYLE_CHOICES + if not self.configured_data['DEFAULT_STYLE'] in [s for s, l in self.configured_data['STYLE_CHOICES']]: + self.configured_data['DEFAULT_STYLE'] = '' + return self.configured_data diff --git a/cmsplugin_filer_image/forms.py b/cmsplugin_filer_image/forms.py new file mode 100644 index 00000000..6d95db9d --- /dev/null +++ b/cmsplugin_filer_image/forms.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django import forms + +from djangocms_attributes_field.widgets import AttributesWidget + +from .models import FilerImage + + +class FilerImageForm(forms.ModelForm): + + class Meta: + model = FilerImage + exclude = [] + + def __init__(self, *args, **kwargs): + super(FilerImageForm, self).__init__(*args, **kwargs) + self.fields['link_attributes'].widget = AttributesWidget() diff --git a/cmsplugin_filer_image/integrations/__init__.py b/cmsplugin_filer_image/integrations/__init__.py new file mode 100644 index 00000000..40a96afc --- /dev/null +++ b/cmsplugin_filer_image/integrations/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/cmsplugin_filer_image/integrations/ckeditor.py b/cmsplugin_filer_image/integrations/ckeditor.py new file mode 100644 index 00000000..4a8b6a44 --- /dev/null +++ b/cmsplugin_filer_image/integrations/ckeditor.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from cms.models.pluginmodel import CMSPlugin +from django.core.files.uploadedfile import SimpleUploadedFile + + +def create_image_plugin(filename, image, parent_plugin, **kwargs): + """ + Used for drag-n-drop image insertion with djangocms-text-ckeditor. + Set TEXT_SAVE_IMAGE_FUNCTION='cmsplugin_filer_image.integrations.ckeditor.create_image_plugin' to enable. + """ + from cmsplugin_filer_image.models import FilerImage + from filer.models import Image + image_plugin = FilerImage() + image_plugin.placeholder = parent_plugin.placeholder + image_plugin.parent = CMSPlugin.objects.get(pk=parent_plugin.id) + image_plugin.position = CMSPlugin.objects.filter(parent=parent_plugin).count() + image_plugin.language = parent_plugin.language + image_plugin.plugin_type = 'FilerImagePlugin' + image.seek(0) + image_model = Image.objects.create(file=SimpleUploadedFile(name=filename, content=image.read())) + image_plugin.image = image_model + image_plugin.save() + return image_plugin + + +def update_image_plugin(plugin, width=None, height=None): + plugin.width = width + plugin.heigt = height + plugin.save() diff --git a/cmsplugin_filer_image/locale/ca/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/ca/LC_MESSAGES/django.po new file mode 100644 index 00000000..a080c950 --- /dev/null +++ b/cmsplugin_filer_image/locale/ca/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Roger Pons , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Catalan (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Imatge" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Opcions de redimensionat d'imatge" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Més" + +#: models.py:25 +msgid "left" +msgstr "esquerra" + +#: models.py:26 +msgid "right" +msgstr "dreta" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "text de la llegenda" + +#: models.py:40 +msgid "image" +msgstr "imatge" + +#: models.py:43 +msgid "alternative image url" +msgstr "url alternativa de la imatge" + +#: models.py:44 +msgid "alt text" +msgstr "text alternatiu" + +#: models.py:46 +msgid "use the original image" +msgstr "utilitza la imatge original" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "no redimensionar la imatge, utilitzar la imatge original." + +#: models.py:49 +msgid "thumbnail option" +msgstr "opció de miniatura" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "sobreescriu l'amplada, l'alçada, el retall i l'augment d'escala amb els valors de l'opció de miniatura seleccionat" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "utilitzar escalat automàtic" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "intenta escalar automàticament la imatge segons el context del marcado de posició" + +#: models.py:53 +msgid "width" +msgstr "amplada" + +#: models.py:54 +msgid "height" +msgstr "alçada" + +#: models.py:55 +msgid "crop" +msgstr "retall" + +#: models.py:56 +msgid "upscale" +msgstr "augment d'escala" + +#: models.py:57 +msgid "image alignment" +msgstr "aliniació de la imatge" + +#: models.py:59 +msgid "link" +msgstr "enllaç" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "si la imatge està present es podrà fer clic" + +#: models.py:63 +msgid "page link" +msgstr "enllaç a pàgina" + +#: models.py:68 +msgid "file link" +msgstr "enllaç a fitxer" + +#: models.py:73 +msgid "link original image" +msgstr "enllaça a la imatge original" + +#: models.py:75 +msgid "description" +msgstr "descripció" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Obre l'enllaç en una finestra nova" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "imatge del filer" + +#: models.py:87 +msgid "filer images" +msgstr "imatges del filer" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Cal seleccionar una imatge o l'url d'una imatge" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Publicació d'Imatge %(caption)s" diff --git a/cmsplugin_filer_image/locale/de/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/de/LC_MESSAGES/django.po new file mode 100644 index 00000000..4c1d148e --- /dev/null +++ b/cmsplugin_filer_image/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,163 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Michal Bielicki , 2013 +# Stefan Foulis , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: German (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Bild" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Bildgrössenanpassungsoptionen" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Mehr" + +#: models.py:25 +msgid "left" +msgstr "links" + +#: models.py:26 +msgid "right" +msgstr "rechts" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "Beschriftungstext" + +#: models.py:40 +msgid "image" +msgstr "Bild" + +#: models.py:43 +msgid "alternative image url" +msgstr "alternative Bild URL" + +#: models.py:44 +msgid "alt text" +msgstr "alt Text" + +#: models.py:46 +msgid "use the original image" +msgstr "Original-Bild verwenden" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "Bildgrösse nicht anpassen, sondern das Original-Bild verwenden." + +#: models.py:49 +msgid "thumbnail option" +msgstr "Thumbnail Optionen" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "Übersteuert Breite, Höhe, Zuschnitt und Vergrösserung mit Werten von der ausgewählten Thumbnail-Option." + +#: models.py:51 +msgid "use automatic scaling" +msgstr "automatisch skalieren" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "versucht die benötigte Bildgröße automatisch an den Seiteninhalt anzupassen" + +#: models.py:53 +msgid "width" +msgstr "Breite" + +#: models.py:54 +msgid "height" +msgstr "Höhe" + +#: models.py:55 +msgid "crop" +msgstr "Zuschneiden" + +#: models.py:56 +msgid "upscale" +msgstr "Vergrößern" + +#: models.py:57 +msgid "image alignment" +msgstr "Bildausrichtung" + +#: models.py:59 +msgid "link" +msgstr "Link" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "wenn angegeben, wird das Bild verlinkt" + +#: models.py:63 +msgid "page link" +msgstr "Seitenlink" + +#: models.py:68 +msgid "file link" +msgstr "Datei-Link" + +#: models.py:73 +msgid "link original image" +msgstr "Original-Bild verlinken" + +#: models.py:75 +msgid "description" +msgstr "Beschreibung" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Link in neuem Fenster öffnen" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "Filer Bild" + +#: models.py:87 +msgid "filer images" +msgstr "Filer Bilder" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Entweder muss ein Bild oder eine Bild-Url ausgewählt sein." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Bildveröffentlichung %(caption)s" diff --git a/cmsplugin_filer_image/locale/en/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..be77a4e4 --- /dev/null +++ b/cmsplugin_filer_image/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,161 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "" + +#: cms_plugins.py:49 +msgid "More" +msgstr "" + +#: models.py:25 +msgid "left" +msgstr "" + +#: models.py:26 +msgid "right" +msgstr "" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "" + +#: models.py:40 +msgid "image" +msgstr "" + +#: models.py:43 +msgid "alternative image url" +msgstr "" + +#: models.py:44 +msgid "alt text" +msgstr "" + +#: models.py:46 +msgid "use the original image" +msgstr "" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "" + +#: models.py:49 +msgid "thumbnail option" +msgstr "" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "" + +#: models.py:53 +msgid "width" +msgstr "" + +#: models.py:54 +msgid "height" +msgstr "" + +#: models.py:55 +msgid "crop" +msgstr "" + +#: models.py:56 +msgid "upscale" +msgstr "" + +#: models.py:57 +msgid "image alignment" +msgstr "" + +#: models.py:59 +msgid "link" +msgstr "" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "" + +#: models.py:63 +msgid "page link" +msgstr "" + +#: models.py:68 +msgid "file link" +msgstr "" + +#: models.py:73 +msgid "link original image" +msgstr "" + +#: models.py:75 +msgid "description" +msgstr "" + +#: models.py:76 +msgid "Open link in new window" +msgstr "" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "" + +#: models.py:87 +msgid "filer images" +msgstr "" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "" diff --git a/cmsplugin_filer_image/locale/es/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..c8d88ae6 --- /dev/null +++ b/cmsplugin_filer_image/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Javier Sánchez , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Spanish (http://www.transifex.com/divio/cmsplugin-filer/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Imagen" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Opciones de redimensionado de imagen" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Más" + +#: models.py:25 +msgid "left" +msgstr "izquierda" + +#: models.py:26 +msgid "right" +msgstr "derecha" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "pie de foto" + +#: models.py:40 +msgid "image" +msgstr "imagen" + +#: models.py:43 +msgid "alternative image url" +msgstr "url de la imagen alternativa" + +#: models.py:44 +msgid "alt text" +msgstr "texto alternativo" + +#: models.py:46 +msgid "use the original image" +msgstr "usar la imagen original" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "no redimensionar la imagen. usar la imagen original." + +#: models.py:49 +msgid "thumbnail option" +msgstr "opción de miniatura" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "sobreescribe ancho, alto, recorta y escala con los valores de la opción de miniatura seleccionada" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "usar el escalado automático" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "intenta autoescalar la imagen basado en el contexto del placeholder" + +#: models.py:53 +msgid "width" +msgstr "ancho" + +#: models.py:54 +msgid "height" +msgstr "alto" + +#: models.py:55 +msgid "crop" +msgstr "recortar" + +#: models.py:56 +msgid "upscale" +msgstr "autoescalar" + +#: models.py:57 +msgid "image alignment" +msgstr "alineación de la imagen" + +#: models.py:59 +msgid "link" +msgstr "enlace" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "podrá hacerse click en la imagen actual" + +#: models.py:63 +msgid "page link" +msgstr "enlace de página" + +#: models.py:68 +msgid "file link" +msgstr "enlace de archivo" + +#: models.py:73 +msgid "link original image" +msgstr "enlace a la imagen original " + +#: models.py:75 +msgid "description" +msgstr "descripción" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Abrir el enlace en una nueva ventana" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "imagen filer" + +#: models.py:87 +msgid "filer images" +msgstr "imágenes filer" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Al menos una imagen o una url de imagen debe ser seleccionada." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Publicación de Imagen %(caption)s" diff --git a/cmsplugin_filer_image/locale/et/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/et/LC_MESSAGES/django.po new file mode 100644 index 00000000..6d672e67 --- /dev/null +++ b/cmsplugin_filer_image/locale/et/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rivo Zängov , 2012-2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Estonian (http://www.transifex.com/divio/cmsplugin-filer/language/et/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Pilt" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Pildi suuruse muutmise valikud" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Veel" + +#: models.py:25 +msgid "left" +msgstr "vasakule" + +#: models.py:26 +msgid "right" +msgstr "paremale" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "pealkirja tekst" + +#: models.py:40 +msgid "image" +msgstr "pilt" + +#: models.py:43 +msgid "alternative image url" +msgstr "alternatiivne pildi url" + +#: models.py:44 +msgid "alt text" +msgstr "alt tekst" + +#: models.py:46 +msgid "use the original image" +msgstr "kasuta algset pilti" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "ära muuda pildi suurust. kasuta hoopis algset pilti." + +#: models.py:49 +msgid "thumbnail option" +msgstr "pisipildi valik" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "kasuta automaatset suuruse muutmist" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "" + +#: models.py:53 +msgid "width" +msgstr "laius" + +#: models.py:54 +msgid "height" +msgstr "kõrgus" + +#: models.py:55 +msgid "crop" +msgstr "lõika" + +#: models.py:56 +msgid "upscale" +msgstr "suurenda" + +#: models.py:57 +msgid "image alignment" +msgstr "pildi joondus" + +#: models.py:59 +msgid "link" +msgstr "link" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "" + +#: models.py:63 +msgid "page link" +msgstr "lehe link" + +#: models.py:68 +msgid "file link" +msgstr "faili link" + +#: models.py:73 +msgid "link original image" +msgstr "link algsele pildile" + +#: models.py:75 +msgid "description" +msgstr "kirjeldus" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Ava link uues aknas" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "pilt" + +#: models.py:87 +msgid "filer images" +msgstr "pildid" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "" diff --git a/cmsplugin_filer_image/locale/eu/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/eu/LC_MESSAGES/django.po new file mode 100644 index 00000000..ba978e63 --- /dev/null +++ b/cmsplugin_filer_image/locale/eu/LC_MESSAGES/django.po @@ -0,0 +1,161 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Basque (http://www.transifex.com/divio/cmsplugin-filer/language/eu/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: eu\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Irudia" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Irudiaren tamaina aldatze aukerak" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Gehiago" + +#: models.py:25 +msgid "left" +msgstr "ezkerrean" + +#: models.py:26 +msgid "right" +msgstr "eskuinean" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "epigrafeko testua" + +#: models.py:40 +msgid "image" +msgstr "irudia" + +#: models.py:43 +msgid "alternative image url" +msgstr "ordezko irudiaren helbidea" + +#: models.py:44 +msgid "alt text" +msgstr "testu alternatiboa" + +#: models.py:46 +msgid "use the original image" +msgstr "jatorrizko irudia erabili" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "ez aldatu irudiaren tamaina, jatorrizko irudia erabili." + +#: models.py:49 +msgid "thumbnail option" +msgstr "argazkitxoaren aukerak" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "zabalera, altuera, mozketa eta handitzea hautatutako argazkitxoen aukeratik hartu" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "eskalatze automatikoa erabili" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "leku-markaren testuinguruan oinarrituta irudia automatikoki eskalatzen saiatu" + +#: models.py:53 +msgid "width" +msgstr "zabalera" + +#: models.py:54 +msgid "height" +msgstr "altuera" + +#: models.py:55 +msgid "crop" +msgstr "moztu" + +#: models.py:56 +msgid "upscale" +msgstr "handitu" + +#: models.py:57 +msgid "image alignment" +msgstr "orriaren lerrokatzea" + +#: models.py:59 +msgid "link" +msgstr "esteka" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "egotekotan irudia klikagarria izango da" + +#: models.py:63 +msgid "page link" +msgstr "orri esteka" + +#: models.py:68 +msgid "file link" +msgstr "fitxategi esteka" + +#: models.py:73 +msgid "link original image" +msgstr "jatorrizko irudira estekatu" + +#: models.py:75 +msgid "description" +msgstr "deskribapena" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Ireki esteka orri berrian" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "filer irudia" + +#: models.py:87 +msgid "filer images" +msgstr "filer irudiak" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Irudi edo helbidea, bietako bat hautatu behar da." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Irudi Argitalpena %(caption)s" diff --git a/cmsplugin_filer_image/locale/fi/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/fi/LC_MESSAGES/django.po new file mode 100644 index 00000000..978cbe87 --- /dev/null +++ b/cmsplugin_filer_image/locale/fi/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Klaus Dahlén , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Finnish (http://www.transifex.com/divio/cmsplugin-filer/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Kuva" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Kuvakoon asetukset" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Lisää" + +#: models.py:25 +msgid "left" +msgstr "vasen" + +#: models.py:26 +msgid "right" +msgstr "oikea" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "kuvateksti" + +#: models.py:40 +msgid "image" +msgstr "kuva" + +#: models.py:43 +msgid "alternative image url" +msgstr "vaihtoehtoisen kuvan URL" + +#: models.py:44 +msgid "alt text" +msgstr "alt-teksti" + +#: models.py:46 +msgid "use the original image" +msgstr "" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "" + +#: models.py:49 +msgid "thumbnail option" +msgstr "pikkukuvan asetukset" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "käytä automaattista skaalausta" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "pyrkii skaalaamaan kuvan automaattisesti sisältöalueen kontekstin perusteella" + +#: models.py:53 +msgid "width" +msgstr "leveys" + +#: models.py:54 +msgid "height" +msgstr "korkeus" + +#: models.py:55 +msgid "crop" +msgstr "rajaus" + +#: models.py:56 +msgid "upscale" +msgstr "suurennus" + +#: models.py:57 +msgid "image alignment" +msgstr "kuvan asemointi" + +#: models.py:59 +msgid "link" +msgstr "linkki" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "kuva on klikattavissa mikäli tämä on täytetty" + +#: models.py:63 +msgid "page link" +msgstr "sivulinkki" + +#: models.py:68 +msgid "file link" +msgstr "tiedostolinkki" + +#: models.py:73 +msgid "link original image" +msgstr "linkitä alkuperäiseen kuvaan" + +#: models.py:75 +msgid "description" +msgstr "kuvaus" + +#: models.py:76 +msgid "Open link in new window" +msgstr "" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "filer-kuva" + +#: models.py:87 +msgid "filer images" +msgstr "filer-kuvat" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Joko kuva tai kuvan URL on valittava." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Kuvan julkaisu %(caption)s" diff --git a/cmsplugin_filer_image/locale/fr/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..e1e4b632 --- /dev/null +++ b/cmsplugin_filer_image/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,165 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Bertrand Bordage , 2012 +# Bertrand Bordage , 2012 +# Frigory33 , 2016 +# Dylann CORDEL , 2014 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-06 18:34+0000\n" +"Last-Translator: Frigory33 \n" +"Language-Team: French (http://www.transifex.com/divio/cmsplugin-filer/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "image du gestionnaire de fichiers de django" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Image" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Options de redimensionnement de l'image" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Paramètres avancés" + +#: models.py:25 +msgid "left" +msgstr "gauche" + +#: models.py:26 +msgid "right" +msgstr "droite" + +#: models.py:27 +msgid "center" +msgstr "centre" + +#: models.py:34 +msgid "Style" +msgstr "Style" + +#: models.py:35 +msgid "caption text" +msgstr "légende" + +#: models.py:40 +msgid "image" +msgstr "image" + +#: models.py:43 +msgid "alternative image url" +msgstr "adresse alternative de l'image" + +#: models.py:44 +msgid "alt text" +msgstr "texte alternatif" + +#: models.py:46 +msgid "use the original image" +msgstr "utiliser l'image d'origine" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "ne pas redimensionner l'image ; utiliser l'image d'origine" + +#: models.py:49 +msgid "thumbnail option" +msgstr "option de miniature" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "écrase les valeurs de largeur, hauteur, rognage et agrandissement avec les valeurs de l'option de miniature sélectionnée" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "redimensionnement automatique" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "essaie de redimensionner automatiquement l'image à partir du contexte de la portion de page" + +#: models.py:53 +msgid "width" +msgstr "largeur" + +#: models.py:54 +msgid "height" +msgstr "hauteur" + +#: models.py:55 +msgid "crop" +msgstr "rogner" + +#: models.py:56 +msgid "upscale" +msgstr "élargir" + +#: models.py:57 +msgid "image alignment" +msgstr "alignement de l'image" + +#: models.py:59 +msgid "link" +msgstr "lien" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "si la présente image sera cliquable" + +#: models.py:63 +msgid "page link" +msgstr "lien vers une page" + +#: models.py:68 +msgid "file link" +msgstr "lien vers un fichier" + +#: models.py:73 +msgid "link original image" +msgstr "lien vers l'image d'origine" + +#: models.py:75 +msgid "description" +msgstr "description" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Ouvre le lien dans une nouvelle fenêtre" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Facultatif. Ajoute des attributs HTML au lien généré." + +#: models.py:86 +msgid "filer image" +msgstr "image du gestionnaire de fichiers" + +#: models.py:87 +msgid "filer images" +msgstr "images du gestionnaire de fichiers" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Une image ou bien une adresse d'image doit être sélectionnée." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Publication d'image %(caption)s" diff --git a/cmsplugin_filer_image/locale/it/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/it/LC_MESSAGES/django.po new file mode 100644 index 00000000..c3c384e4 --- /dev/null +++ b/cmsplugin_filer_image/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,164 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# yakky , 2012 +# yakky , 2013,2016 +# yakky , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-13 08:28+0000\n" +"Last-Translator: yakky \n" +"Language-Team: Italian (http://www.transifex.com/divio/cmsplugin-filer/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "immagine django filer" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Immagine" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Opzioni ridimensionamento immagine" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Vedi" + +#: models.py:25 +msgid "left" +msgstr "sinistra" + +#: models.py:26 +msgid "right" +msgstr "destra" + +#: models.py:27 +msgid "center" +msgstr "centrato" + +#: models.py:34 +msgid "Style" +msgstr "Stile" + +#: models.py:35 +msgid "caption text" +msgstr "didascalia" + +#: models.py:40 +msgid "image" +msgstr "immagine" + +#: models.py:43 +msgid "alternative image url" +msgstr "URL alternativo per l'immagine" + +#: models.py:44 +msgid "alt text" +msgstr "testo alternativo" + +#: models.py:46 +msgid "use the original image" +msgstr "usa l'immagine originale" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "non ridimensionare l'immagine. Usa l'immagine originale." + +#: models.py:49 +msgid "thumbnail option" +msgstr "opzione anteprima immagine" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "ignora le impostazioni di larghezza, altezza, taglio e riscalatura e usa l'opzione anteprima immagine selezionata" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "usa ridimensionamento automatico" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "prova a dimensionare l'immagine in base al contesto del placeholder" + +#: models.py:53 +msgid "width" +msgstr "larghezza" + +#: models.py:54 +msgid "height" +msgstr "altezza" + +#: models.py:55 +msgid "crop" +msgstr "taglia" + +#: models.py:56 +msgid "upscale" +msgstr "ingrandisci" + +#: models.py:57 +msgid "image alignment" +msgstr "allineamento immagine" + +#: models.py:59 +msgid "link" +msgstr "link" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "se presente, l'immagine sarà cliccabile" + +#: models.py:63 +msgid "page link" +msgstr "link a pagina" + +#: models.py:68 +msgid "file link" +msgstr "collegamento a file" + +#: models.py:73 +msgid "link original image" +msgstr "collegamento all'immagine originale" + +#: models.py:75 +msgid "description" +msgstr "descrizione" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Apri collegamento in una nuova finestra" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Opzionale. Aggiungi attributi HTML al link" + +#: models.py:86 +msgid "filer image" +msgstr "immagine" + +#: models.py:87 +msgid "filer images" +msgstr "immagini" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Devi selezionare un'immagine o un indirizzo di immagine" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Pubblicazione immagine %(caption)s" diff --git a/cmsplugin_filer_image/locale/it_IT/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/it_IT/LC_MESSAGES/django.po new file mode 100644 index 00000000..2b065317 --- /dev/null +++ b/cmsplugin_filer_image/locale/it_IT/LC_MESSAGES/django.po @@ -0,0 +1,161 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Italian (Italy) (http://www.transifex.com/divio/cmsplugin-filer/language/it_IT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it_IT\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "" + +#: cms_plugins.py:49 +msgid "More" +msgstr "" + +#: models.py:25 +msgid "left" +msgstr "" + +#: models.py:26 +msgid "right" +msgstr "" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "" + +#: models.py:40 +msgid "image" +msgstr "" + +#: models.py:43 +msgid "alternative image url" +msgstr "" + +#: models.py:44 +msgid "alt text" +msgstr "" + +#: models.py:46 +msgid "use the original image" +msgstr "" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "" + +#: models.py:49 +msgid "thumbnail option" +msgstr "" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "" + +#: models.py:53 +msgid "width" +msgstr "" + +#: models.py:54 +msgid "height" +msgstr "" + +#: models.py:55 +msgid "crop" +msgstr "" + +#: models.py:56 +msgid "upscale" +msgstr "" + +#: models.py:57 +msgid "image alignment" +msgstr "" + +#: models.py:59 +msgid "link" +msgstr "" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "" + +#: models.py:63 +msgid "page link" +msgstr "" + +#: models.py:68 +msgid "file link" +msgstr "" + +#: models.py:73 +msgid "link original image" +msgstr "" + +#: models.py:75 +msgid "description" +msgstr "" + +#: models.py:76 +msgid "Open link in new window" +msgstr "" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "" + +#: models.py:87 +msgid "filer images" +msgstr "" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "" diff --git a/cmsplugin_filer_image/locale/lt/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/lt/LC_MESSAGES/django.po new file mode 100644 index 00000000..34f6c451 --- /dev/null +++ b/cmsplugin_filer_image/locale/lt/LC_MESSAGES/django.po @@ -0,0 +1,163 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Matas Dailyda, 2015 +# Matas Dailyda , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-18 09:18+0000\n" +"Last-Translator: Matas Dailyda \n" +"Language-Team: Lithuanian (http://www.transifex.com/divio/cmsplugin-filer/language/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "django filer paveikslėlis" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Paveikslėlis" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Paveikslėlio dydžio keitimo nustatymai" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Daugiau" + +#: models.py:25 +msgid "left" +msgstr "kairė" + +#: models.py:26 +msgid "right" +msgstr "dešinė" + +#: models.py:27 +msgid "center" +msgstr "centras" + +#: models.py:34 +msgid "Style" +msgstr "Stilius" + +#: models.py:35 +msgid "caption text" +msgstr "antraštės tekstas" + +#: models.py:40 +msgid "image" +msgstr "paveikslėlis" + +#: models.py:43 +msgid "alternative image url" +msgstr "alternatyvus paveikslėlio URL" + +#: models.py:44 +msgid "alt text" +msgstr "papildomas tekstas" + +#: models.py:46 +msgid "use the original image" +msgstr "naudoti originalų paveikslėlį" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "nekeisti paveikslėlio dydžio. Naudoti originalų paveikslėlį." + +#: models.py:49 +msgid "thumbnail option" +msgstr "miniatiūros nustatymas" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "perrašo plotį, aukštį, apkirpimą ir mastelį su reikšmemis iš pasirinkto miniatiūros nustatymo" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "naudoti automatinį mastelį" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "bando automatiškai nustatyti paveikslėlio mastelį remiantis rezervuotos vietos kontekstu" + +#: models.py:53 +msgid "width" +msgstr "plotis" + +#: models.py:54 +msgid "height" +msgstr "aukštis" + +#: models.py:55 +msgid "crop" +msgstr "apkirpti" + +#: models.py:56 +msgid "upscale" +msgstr "išdidinti" + +#: models.py:57 +msgid "image alignment" +msgstr "paveikslėlio lygiavimas" + +#: models.py:59 +msgid "link" +msgstr "nuoroda" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "esant, ant paveikslėlio bus galima paspausti" + +#: models.py:63 +msgid "page link" +msgstr "puslapio nuoroda" + +#: models.py:68 +msgid "file link" +msgstr "bylos nuoroda" + +#: models.py:73 +msgid "link original image" +msgstr "nurodyti į originalų paveikslėlį" + +#: models.py:75 +msgid "description" +msgstr "aprašymas" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Atverti nuorodą naujame lange" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Nebūtina. Prideda HTML atributus prie atvaizduojamos nuorodos." + +#: models.py:86 +msgid "filer image" +msgstr "filer paveikslėlis" + +#: models.py:87 +msgid "filer images" +msgstr "filer paveikslėliai" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Turi būti pasirinktas paveikslėlis, arba įvestas paveikslėlio URL" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Paveikslėlio publikavimas %(caption)s" diff --git a/cmsplugin_filer_image/locale/lt_LT/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/lt_LT/LC_MESSAGES/django.po new file mode 100644 index 00000000..3f92a3c7 --- /dev/null +++ b/cmsplugin_filer_image/locale/lt_LT/LC_MESSAGES/django.po @@ -0,0 +1,161 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Lithuanian (Lithuania) (http://www.transifex.com/divio/cmsplugin-filer/language/lt_LT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt_LT\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "" + +#: cms_plugins.py:49 +msgid "More" +msgstr "" + +#: models.py:25 +msgid "left" +msgstr "" + +#: models.py:26 +msgid "right" +msgstr "" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "" + +#: models.py:40 +msgid "image" +msgstr "" + +#: models.py:43 +msgid "alternative image url" +msgstr "" + +#: models.py:44 +msgid "alt text" +msgstr "" + +#: models.py:46 +msgid "use the original image" +msgstr "" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "" + +#: models.py:49 +msgid "thumbnail option" +msgstr "" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "" + +#: models.py:53 +msgid "width" +msgstr "" + +#: models.py:54 +msgid "height" +msgstr "" + +#: models.py:55 +msgid "crop" +msgstr "" + +#: models.py:56 +msgid "upscale" +msgstr "" + +#: models.py:57 +msgid "image alignment" +msgstr "" + +#: models.py:59 +msgid "link" +msgstr "" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "" + +#: models.py:63 +msgid "page link" +msgstr "" + +#: models.py:68 +msgid "file link" +msgstr "" + +#: models.py:73 +msgid "link original image" +msgstr "" + +#: models.py:75 +msgid "description" +msgstr "" + +#: models.py:76 +msgid "Open link in new window" +msgstr "" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "" + +#: models.py:87 +msgid "filer images" +msgstr "" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "" diff --git a/cmsplugin_filer_image/locale/nb/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/nb/LC_MESSAGES/django.po new file mode 100644 index 00000000..ecc019fe --- /dev/null +++ b/cmsplugin_filer_image/locale/nb/LC_MESSAGES/django.po @@ -0,0 +1,161 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Bokmål (http://www.transifex.com/divio/cmsplugin-filer/language/nb/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nb\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "" + +#: cms_plugins.py:49 +msgid "More" +msgstr "" + +#: models.py:25 +msgid "left" +msgstr "" + +#: models.py:26 +msgid "right" +msgstr "" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "" + +#: models.py:40 +msgid "image" +msgstr "" + +#: models.py:43 +msgid "alternative image url" +msgstr "" + +#: models.py:44 +msgid "alt text" +msgstr "" + +#: models.py:46 +msgid "use the original image" +msgstr "" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "" + +#: models.py:49 +msgid "thumbnail option" +msgstr "" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "" + +#: models.py:53 +msgid "width" +msgstr "" + +#: models.py:54 +msgid "height" +msgstr "" + +#: models.py:55 +msgid "crop" +msgstr "" + +#: models.py:56 +msgid "upscale" +msgstr "" + +#: models.py:57 +msgid "image alignment" +msgstr "" + +#: models.py:59 +msgid "link" +msgstr "" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "" + +#: models.py:63 +msgid "page link" +msgstr "" + +#: models.py:68 +msgid "file link" +msgstr "" + +#: models.py:73 +msgid "link original image" +msgstr "" + +#: models.py:75 +msgid "description" +msgstr "" + +#: models.py:76 +msgid "Open link in new window" +msgstr "" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "" + +#: models.py:87 +msgid "filer images" +msgstr "" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "" diff --git a/cmsplugin_filer_image/locale/nl/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/nl/LC_MESSAGES/django.po new file mode 100644 index 00000000..cba504c3 --- /dev/null +++ b/cmsplugin_filer_image/locale/nl/LC_MESSAGES/django.po @@ -0,0 +1,164 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Maarten Draijer , 2013 +# Maikel Wever, 2013 +# kurdyma , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-27 10:26+0000\n" +"Last-Translator: kurdyma \n" +"Language-Team: Dutch (http://www.transifex.com/divio/cmsplugin-filer/language/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "django filer afbeelding" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Afbeelding" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Opties voor het schalen van de afbeelding" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Meer" + +#: models.py:25 +msgid "left" +msgstr "links" + +#: models.py:26 +msgid "right" +msgstr "rechts" + +#: models.py:27 +msgid "center" +msgstr "centreer" + +#: models.py:34 +msgid "Style" +msgstr "Stijl" + +#: models.py:35 +msgid "caption text" +msgstr "bijschrift tekst" + +#: models.py:40 +msgid "image" +msgstr "afbeelding" + +#: models.py:43 +msgid "alternative image url" +msgstr "alternatieve afbeeldingslocatie" + +#: models.py:44 +msgid "alt text" +msgstr "alt tekst" + +#: models.py:46 +msgid "use the original image" +msgstr "gebruik de originele afbeelding" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "afbeelding niet schalen, gebruik in plaats daarvan de originele afbeelding." + +#: models.py:49 +msgid "thumbnail option" +msgstr "thumbnailoptie" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "overschrijft breedte, hoogte, uitsnijding en opschaling met waardes van de geselecteerde thumnbailoptie" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "gebruik automatische schaling" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "probeert de afbeelding automatisch te schalen gebaseerd op de placeholder context" + +#: models.py:53 +msgid "width" +msgstr "breedte" + +#: models.py:54 +msgid "height" +msgstr "hoogte" + +#: models.py:55 +msgid "crop" +msgstr "uitsnijden" + +#: models.py:56 +msgid "upscale" +msgstr "opschalen" + +#: models.py:57 +msgid "image alignment" +msgstr "afbeeldingsuitlijning" + +#: models.py:59 +msgid "link" +msgstr "link" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "indien present zal de afbeelding klikbaar zijn" + +#: models.py:63 +msgid "page link" +msgstr "paginalink" + +#: models.py:68 +msgid "file link" +msgstr "bestandslink" + +#: models.py:73 +msgid "link original image" +msgstr "link originele afbeelding" + +#: models.py:75 +msgid "description" +msgstr "omschrijving" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Open link in een nieuw venster" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Optioneel. Voegt HTML attributen toe aan gerenderde link." + +#: models.py:86 +msgid "filer image" +msgstr "filer afbeelding" + +#: models.py:87 +msgid "filer images" +msgstr "filer afbeeldingen" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Er moet of een afbeelding, of een afbeeldingslocatie worden geselecteerd." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Afbeelding Publicatie %(caption)s" diff --git a/cmsplugin_filer_image/locale/nn/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/nn/LC_MESSAGES/django.po new file mode 100644 index 00000000..dd80bbfd --- /dev/null +++ b/cmsplugin_filer_image/locale/nn/LC_MESSAGES/django.po @@ -0,0 +1,161 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Nynorsk (http://www.transifex.com/divio/cmsplugin-filer/language/nn/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nn\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "" + +#: cms_plugins.py:49 +msgid "More" +msgstr "" + +#: models.py:25 +msgid "left" +msgstr "" + +#: models.py:26 +msgid "right" +msgstr "" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "" + +#: models.py:40 +msgid "image" +msgstr "" + +#: models.py:43 +msgid "alternative image url" +msgstr "" + +#: models.py:44 +msgid "alt text" +msgstr "" + +#: models.py:46 +msgid "use the original image" +msgstr "" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "" + +#: models.py:49 +msgid "thumbnail option" +msgstr "" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "" + +#: models.py:53 +msgid "width" +msgstr "" + +#: models.py:54 +msgid "height" +msgstr "" + +#: models.py:55 +msgid "crop" +msgstr "" + +#: models.py:56 +msgid "upscale" +msgstr "" + +#: models.py:57 +msgid "image alignment" +msgstr "" + +#: models.py:59 +msgid "link" +msgstr "" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "" + +#: models.py:63 +msgid "page link" +msgstr "" + +#: models.py:68 +msgid "file link" +msgstr "" + +#: models.py:73 +msgid "link original image" +msgstr "" + +#: models.py:75 +msgid "description" +msgstr "" + +#: models.py:76 +msgid "Open link in new window" +msgstr "" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "" + +#: models.py:87 +msgid "filer images" +msgstr "" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "" diff --git a/cmsplugin_filer_image/locale/pl_PL/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/pl_PL/LC_MESSAGES/django.po new file mode 100644 index 00000000..4a6ae2c9 --- /dev/null +++ b/cmsplugin_filer_image/locale/pl_PL/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Mateusz Marzantowicz , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/divio/cmsplugin-filer/language/pl_PL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Obraz" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Opcje zmiany rozmiaru" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Więcej" + +#: models.py:25 +msgid "left" +msgstr "" + +#: models.py:26 +msgid "right" +msgstr "" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "" + +#: models.py:40 +msgid "image" +msgstr "obraz" + +#: models.py:43 +msgid "alternative image url" +msgstr "" + +#: models.py:44 +msgid "alt text" +msgstr "tekst alternatywny" + +#: models.py:46 +msgid "use the original image" +msgstr "użyj oryginalnego obrazu" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "Nie zmieniaj rozmiaru, użyj oryginalnego obrazu." + +#: models.py:49 +msgid "thumbnail option" +msgstr "opcje miniatury" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "użyj automatycznego skalowania" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "" + +#: models.py:53 +msgid "width" +msgstr "szerokość" + +#: models.py:54 +msgid "height" +msgstr "wysokość" + +#: models.py:55 +msgid "crop" +msgstr "przytnij" + +#: models.py:56 +msgid "upscale" +msgstr "skaluj" + +#: models.py:57 +msgid "image alignment" +msgstr "wyrównanie obrazu" + +#: models.py:59 +msgid "link" +msgstr "odnośnik" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "" + +#: models.py:63 +msgid "page link" +msgstr "" + +#: models.py:68 +msgid "file link" +msgstr "odnośnik do pliku" + +#: models.py:73 +msgid "link original image" +msgstr "" + +#: models.py:75 +msgid "description" +msgstr "opis" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Otwórz odnośnik w nowym oknie" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "" + +#: models.py:87 +msgid "filer images" +msgstr "" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "" diff --git a/cmsplugin_filer_image/locale/pt_BR/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 00000000..dc6d0bb1 --- /dev/null +++ b/cmsplugin_filer_image/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rodrigo , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/divio/cmsplugin-filer/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Imagem" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "opções de redimensionamento de imagem" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Mais" + +#: models.py:25 +msgid "left" +msgstr "esquerda" + +#: models.py:26 +msgid "right" +msgstr "direita" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "texto de caption" + +#: models.py:40 +msgid "image" +msgstr "imagem" + +#: models.py:43 +msgid "alternative image url" +msgstr "url alternativa para imagem" + +#: models.py:44 +msgid "alt text" +msgstr "alt text" + +#: models.py:46 +msgid "use the original image" +msgstr "utilizar a imagem original" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "não redimensionar a imagem. ao invés disso utilizar a imagem original." + +#: models.py:49 +msgid "thumbnail option" +msgstr "opção da miniatura" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "Ignorar largura, altura, recorte ou dimensionamento através dos valores das opções de miniaturas selecionados." + +#: models.py:51 +msgid "use automatic scaling" +msgstr "utilizar o escalamento automático" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "tentar auto formatar as dimensões da imagem baseado no contexto do placeholder" + +#: models.py:53 +msgid "width" +msgstr "largura" + +#: models.py:54 +msgid "height" +msgstr "altura" + +#: models.py:55 +msgid "crop" +msgstr "recortar" + +#: models.py:56 +msgid "upscale" +msgstr "aumentar" + +#: models.py:57 +msgid "image alignment" +msgstr "alinhamento da imagem" + +#: models.py:59 +msgid "link" +msgstr "link" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "se presente a imagem será clicável" + +#: models.py:63 +msgid "page link" +msgstr "link da página" + +#: models.py:68 +msgid "file link" +msgstr "link do arquivo" + +#: models.py:73 +msgid "link original image" +msgstr "link original para a imagem" + +#: models.py:75 +msgid "description" +msgstr "descrição" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Abrir link em nova janela" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "imagem do filer" + +#: models.py:87 +msgid "filer images" +msgstr "imagens do filer" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Uma imagem ou uma url de imagem precisa ser selecionada." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Publicação da Imagem %(caption)s" diff --git a/cmsplugin_filer_image/locale/ru/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 00000000..af2c19b5 --- /dev/null +++ b/cmsplugin_filer_image/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,163 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Pavel , 2012 +# Pavel , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Russian (http://www.transifex.com/divio/cmsplugin-filer/language/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "Изображение" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "Опции изменения размеров изображения" + +#: cms_plugins.py:49 +msgid "More" +msgstr "Больше" + +#: models.py:25 +msgid "left" +msgstr "лево" + +#: models.py:26 +msgid "right" +msgstr "право" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "текст заголовка" + +#: models.py:40 +msgid "image" +msgstr "изображение" + +#: models.py:43 +msgid "alternative image url" +msgstr "альтернативная ссылка на изображение" + +#: models.py:44 +msgid "alt text" +msgstr "альтернативный текст" + +#: models.py:46 +msgid "use the original image" +msgstr "использовать оригинальное изображение" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "не менять размер изображения, использовать оригинал" + +#: models.py:49 +msgid "thumbnail option" +msgstr "опция миниатюры" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "переопределяет ширину, высоту, кадрирование и масштабирование значениями из выбранной опции" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "использовать автоматическое масштабирование" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "попытка масштабировать изображение, основываясь на контексте слота" + +#: models.py:53 +msgid "width" +msgstr "ширина" + +#: models.py:54 +msgid "height" +msgstr "высота" + +#: models.py:55 +msgid "crop" +msgstr "обрезать" + +#: models.py:56 +msgid "upscale" +msgstr "увеличивать" + +#: models.py:57 +msgid "image alignment" +msgstr "выравнивание изображения" + +#: models.py:59 +msgid "link" +msgstr "ссылка" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "если установлено, изображение будет кликабельным" + +#: models.py:63 +msgid "page link" +msgstr "ссылка на страницу" + +#: models.py:68 +msgid "file link" +msgstr "ссылка на файл" + +#: models.py:73 +msgid "link original image" +msgstr "ссылка на оригинальное изображение" + +#: models.py:75 +msgid "description" +msgstr "описание" + +#: models.py:76 +msgid "Open link in new window" +msgstr "Открыть ссылку в новом окне" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "изображение (filer)" + +#: models.py:87 +msgid "filer images" +msgstr "изображения (filer)" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "Изображение либо ссылка на изображение должны быть установлены." + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "Публикация изображения %(caption)s" diff --git a/cmsplugin_filer_image/locale/zh_CN/LC_MESSAGES/django.po b/cmsplugin_filer_image/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 00000000..db1c4e6a --- /dev/null +++ b/cmsplugin_filer_image/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,162 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Calvin-He , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Chinese (China) (http://www.transifex.com/divio/cmsplugin-filer/language/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: apps.py:7 +msgid "django filer image" +msgstr "" + +#: cms_plugins.py:21 +msgid "Image" +msgstr "图片" + +#: cms_plugins.py:37 +msgid "Image resizing options" +msgstr "图片缩放选项" + +#: cms_plugins.py:49 +msgid "More" +msgstr "更多" + +#: models.py:25 +msgid "left" +msgstr "左边" + +#: models.py:26 +msgid "right" +msgstr "右边" + +#: models.py:27 +msgid "center" +msgstr "" + +#: models.py:34 +msgid "Style" +msgstr "" + +#: models.py:35 +msgid "caption text" +msgstr "描述文本" + +#: models.py:40 +msgid "image" +msgstr "图片" + +#: models.py:43 +msgid "alternative image url" +msgstr "可选的图片链接" + +#: models.py:44 +msgid "alt text" +msgstr "可选文本" + +#: models.py:46 +msgid "use the original image" +msgstr "使用原始图片" + +#: models.py:47 +msgid "do not resize the image. use the original image instead." +msgstr "不缩放图片大小,而使用原始大小的图片。" + +#: models.py:49 +msgid "thumbnail option" +msgstr "缩略图选项" + +#: models.py:50 +msgid "" +"overrides width, height, crop and upscale with values from the selected " +"thumbnail option" +msgstr "从选择的缩略图选项中设置宽度,高度,crop和upscale" + +#: models.py:51 +msgid "use automatic scaling" +msgstr "自动缩放" + +#: models.py:52 +msgid "tries to auto scale the image based on the placeholder context" +msgstr "基于placeholder上下文来自动缩放图片" + +#: models.py:53 +msgid "width" +msgstr "宽" + +#: models.py:54 +msgid "height" +msgstr "高" + +#: models.py:55 +msgid "crop" +msgstr "" + +#: models.py:56 +msgid "upscale" +msgstr "" + +#: models.py:57 +msgid "image alignment" +msgstr "图片对齐方法" + +#: models.py:59 +msgid "link" +msgstr "链接" + +#: models.py:60 models.py:62 models.py:69 models.py:74 +msgid "if present image will be clickable" +msgstr "图片是否可点击" + +#: models.py:63 +msgid "page link" +msgstr "页面链接" + +#: models.py:68 +msgid "file link" +msgstr "文件链接" + +#: models.py:73 +msgid "link original image" +msgstr "链接原始图片" + +#: models.py:75 +msgid "description" +msgstr "描述" + +#: models.py:76 +msgid "Open link in new window" +msgstr "在新窗口中打开链接" + +#: models.py:78 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" + +#: models.py:86 +msgid "filer image" +msgstr "" + +#: models.py:87 +msgid "filer images" +msgstr "" + +#: models.py:93 +msgid "Either an image or an image url must be selected." +msgstr "图片或图片链接必须选择一个" + +#: models.py:99 +#, python-format +msgid "Image Publication %(caption)s" +msgstr "发布图片%(caption)s" diff --git a/cmsplugin_filer_image/migrations/0001_initial.py b/cmsplugin_filer_image/migrations/0001_initial.py new file mode 100644 index 00000000..6d2a4872 --- /dev/null +++ b/cmsplugin_filer_image/migrations/0001_initial.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.conf import settings +import filer.fields.file +import filer.fields.image +from filer.settings import FILER_IMAGE_MODEL +import cms.models.fields + +FILER_MODEL = FILER_IMAGE_MODEL or 'filer.Image' + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0003_auto_20140926_2347'), + ('filer', '0001_initial'), + migrations.swappable_dependency(FILER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='FilerImage', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE)), + ('style', models.CharField(max_length=50, verbose_name='Style', default=settings.CMSPLUGIN_FILER_IMAGE_DEFAULT_STYLE, blank=True, choices=settings.CMSPLUGIN_FILER_IMAGE_STYLE_CHOICES)), + ('caption_text', models.CharField(max_length=255, null=True, verbose_name='caption text', blank=True)), + ('image_url', models.URLField(default=None, null=True, verbose_name='alternative image url', blank=True)), + ('alt_text', models.CharField(max_length=255, null=True, verbose_name='alt text', blank=True)), + ('use_original_image', models.BooleanField(default=False, help_text='do not resize the image. use the original image instead.', verbose_name='use the original image')), + ('use_autoscale', models.BooleanField(default=False, help_text='tries to auto scale the image based on the placeholder context', verbose_name='use automatic scaling')), + ('width', models.PositiveIntegerField(null=True, verbose_name='width', blank=True)), + ('height', models.PositiveIntegerField(null=True, verbose_name='height', blank=True)), + ('crop', models.BooleanField(default=True, verbose_name='crop')), + ('upscale', models.BooleanField(default=True, verbose_name='upscale')), + ('alignment', models.CharField(blank=True, max_length=10, null=True, verbose_name='image alignment', choices=[('left', 'left'), ('right', 'right')])), + ('free_link', models.CharField(help_text='if present image will be clickable', max_length=255, null=True, verbose_name='link', blank=True)), + ('original_link', models.BooleanField(default=False, help_text='if present image will be clickable', verbose_name='link original image')), + ('description', models.TextField(null=True, verbose_name='description', blank=True)), + ('target_blank', models.BooleanField(default=False, verbose_name='Open link in new window')), + ('file_link', filer.fields.file.FilerFileField(related_name='+', default=None, to='filer.File', blank=True, help_text='if present image will be clickable', null=True, verbose_name='file link', on_delete=models.CASCADE)), + ('image', filer.fields.image.FilerImageField(default=None, blank=True, to='filer.Image', null=True, verbose_name='image', on_delete=models.CASCADE)), + ('page_link', cms.models.fields.PageField(blank=True, to='cms.Page', help_text='if present image will be clickable', null=True, verbose_name='page link')), + ], + options={ + 'verbose_name': 'filer image', + 'verbose_name_plural': 'filer images', + }, + bases=('cms.cmsplugin',), + ), + migrations.CreateModel( + name='ThumbnailOption', + fields=[ + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=100, verbose_name='name')), + ('width', models.IntegerField(help_text='width in pixel.', verbose_name='width')), + ('height', models.IntegerField(help_text='height in pixel.', verbose_name='height')), + ('crop', models.BooleanField(default=True, verbose_name='crop')), + ('upscale', models.BooleanField(default=True, verbose_name='upscale')), + ], + options={ + 'ordering': ('width', 'height'), + 'verbose_name': 'thumbnail option', + 'verbose_name_plural': 'thumbnail options', + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='filerimage', + name='thumbnail_option', + field=models.ForeignKey(blank=True, to='cmsplugin_filer_image.ThumbnailOption', help_text='overrides width, height, crop and upscale with values from the selected thumbnail option', null=True, verbose_name='thumbnail option', on_delete=models.CASCADE), + preserve_default=True, + ), + ] diff --git a/cmsplugin_filer_image/migrations/0002_auto_20160108_1708.py b/cmsplugin_filer_image/migrations/0002_auto_20160108_1708.py new file mode 100644 index 00000000..b3959cff --- /dev/null +++ b/cmsplugin_filer_image/migrations/0002_auto_20160108_1708.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import filer.fields.file +import django.db.models.deletion +import filer.fields.image + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='filerimage', + name='file_link', + field=filer.fields.file.FilerFileField(related_name='+', on_delete=django.db.models.deletion.SET_NULL, default=None, to='filer.File', blank=True, help_text='if present image will be clickable', null=True, verbose_name='file link'), + ), + migrations.AlterField( + model_name='filerimage', + name='image', + field=filer.fields.image.FilerImageField(on_delete=django.db.models.deletion.SET_NULL, default=None, blank=True, to='filer.Image', null=True, verbose_name='image'), + ), + ] diff --git a/cmsplugin_filer_image/migrations/0003_mv_thumbnail_option_to_filer_20160119_1720.py b/cmsplugin_filer_image/migrations/0003_mv_thumbnail_option_to_filer_20160119_1720.py new file mode 100644 index 00000000..9d94698f --- /dev/null +++ b/cmsplugin_filer_image/migrations/0003_mv_thumbnail_option_to_filer_20160119_1720.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + +def move_thumbnail_opt_to_filer(apps, schema_editor): + ThumbnailOption = apps.get_model('cmsplugin_filer_image', 'ThumbnailOption') + ThumbnailOptionNew = apps.get_model('filer', 'ThumbnailOption') + for obj in ThumbnailOption.objects.all(): + try: + ThumbnailOptionNew.objects.get( + name=obj.name, + width=obj.width, + height=obj.height, + crop=obj.crop, + upscale=obj.upscale + ) + except ThumbnailOptionNew.DoesNotExist: + th = ThumbnailOptionNew( + id=obj.id, + name=obj.name, + width=obj.width, + height=obj.height, + crop=obj.crop, + upscale=obj.upscale + ) + th.save() + +def move_thumbnail_opt_to_cms(apps, schema_editor): + ThumbnailOption = apps.get_model('filer', 'ThumbnailOption') + ThumbnailOptionOld = apps.get_model('cmsplugin_filer_image', 'ThumbnailOption') + for obj in ThumbnailOption.objects.all(): + try: + ThumbnailOptionOld.objects.get( + name=obj.name, + width=obj.width, + height=obj.height, + crop=obj.crop, + upscale=obj.upscale + ) + except ThumbnailOptionOld.DoesNotExist: + th = ThumbnailOptionOld( + id=obj.id, + name=obj.name, + width=obj.width, + height=obj.height, + crop=obj.crop, + upscale=obj.upscale + ) + th.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0002_auto_20160108_1708'), + ('filer', '0003_thumbnailoption'), + ] + + operations = [ + migrations.RunPython(move_thumbnail_opt_to_filer, move_thumbnail_opt_to_cms), + ] diff --git a/cmsplugin_filer_image/migrations/0004_auto_20160120_0950.py b/cmsplugin_filer_image/migrations/0004_auto_20160120_0950.py new file mode 100644 index 00000000..df119aad --- /dev/null +++ b/cmsplugin_filer_image/migrations/0004_auto_20160120_0950.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0003_mv_thumbnail_option_to_filer_20160119_1720'), + ] + + operations = [ + migrations.AlterField( + model_name='filerimage', + name='thumbnail_option', + field=models.ForeignKey(blank=True, to='filer.ThumbnailOption', help_text='overrides width, height, crop and upscale with values from the selected thumbnail option', null=True, verbose_name='thumbnail option', on_delete=models.CASCADE), + ), + migrations.DeleteModel( + name='ThumbnailOption', + ), + ] diff --git a/cmsplugin_filer_image/migrations/0005_auto_20160224_1457.py b/cmsplugin_filer_image/migrations/0005_auto_20160224_1457.py new file mode 100644 index 00000000..d861b112 --- /dev/null +++ b/cmsplugin_filer_image/migrations/0005_auto_20160224_1457.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0004_auto_20160120_0950'), + ] + + operations = [ + migrations.AlterField( + model_name='filerimage', + name='free_link', + field=models.CharField(help_text='if present image will be clickable', max_length=2000, null=True, verbose_name='link', blank=True), + ), + ] diff --git a/cmsplugin_filer_image/migrations/0006_auto_20160427_1438.py b/cmsplugin_filer_image/migrations/0006_auto_20160427_1438.py new file mode 100644 index 00000000..d4ee630a --- /dev/null +++ b/cmsplugin_filer_image/migrations/0006_auto_20160427_1438.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0005_auto_20160224_1457'), + ] + + operations = [ + migrations.AlterField( + model_name='filerimage', + name='alignment', + field=models.CharField(blank=True, max_length=10, null=True, verbose_name='image alignment', choices=[('left', 'left'), ('right', 'right'), ('center', 'center')]), + ), + ] diff --git a/cmsplugin_filer_image/migrations/0007_filerimage_link_attributes.py b/cmsplugin_filer_image/migrations/0007_filerimage_link_attributes.py new file mode 100644 index 00000000..63fab92d --- /dev/null +++ b/cmsplugin_filer_image/migrations/0007_filerimage_link_attributes.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0006_auto_20160427_1438'), + ] + + operations = [ + migrations.AddField( + model_name='filerimage', + name='link_attributes', + field=djangocms_attributes_field.fields.AttributesField(help_text='Optional. Adds HTML attributes to the rendered link.', default=dict), + ), + ] diff --git a/cmsplugin_filer_image/migrations/0008_auto_20160705_1334.py b/cmsplugin_filer_image/migrations/0008_auto_20160705_1334.py new file mode 100644 index 00000000..59234075 --- /dev/null +++ b/cmsplugin_filer_image/migrations/0008_auto_20160705_1334.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-05 17:34 +from __future__ import unicode_literals + +from django.db import migrations +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0007_filerimage_link_attributes'), + ] + + operations = [ + migrations.AlterField( + model_name='filerimage', + name='link_attributes', + field=djangocms_attributes_field.fields.AttributesField(blank=True, default=dict, help_text='Optional. Adds HTML attributes to the rendered link.'), + ), + ] diff --git a/cmsplugin_filer_image/migrations/0009_auto_20160713_1853.py b/cmsplugin_filer_image/migrations/0009_auto_20160713_1853.py new file mode 100644 index 00000000..e924acef --- /dev/null +++ b/cmsplugin_filer_image/migrations/0009_auto_20160713_1853.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_image', '0008_auto_20160705_1334'), + ] + + operations = [ + migrations.AlterField( + model_name='filerimage', + name='cmsplugin_ptr', + field=models.OneToOneField(parent_link=True, related_name='cmsplugin_filer_image_filerimage', primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE), + ), + ] diff --git a/cmsplugin_filer_image/migrations/__init__.py b/cmsplugin_filer_image/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cmsplugin_filer_image/models.py b/cmsplugin_filer_image/models.py new file mode 100644 index 00000000..055056b0 --- /dev/null +++ b/cmsplugin_filer_image/models.py @@ -0,0 +1,134 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from filer.fields.file import FilerFileField +from filer.fields.image import FilerImageField +from filer.models import ThumbnailOption # NOQA + +from cms.models import CMSPlugin +from cms.models.fields import PageField +from cmsplugin_filer_utils import FilerPluginManager +from djangocms_attributes_field.fields import AttributesField + +from .conf import settings + + +class FilerImage(CMSPlugin): + LEFT = "left" + RIGHT = "right" + CENTER = "center" + FLOAT_CHOICES = ((LEFT, _("left")), + (RIGHT, _("right")), + (CENTER, _("center")), + ) + STYLE_CHOICES = settings.CMSPLUGIN_FILER_IMAGE_STYLE_CHOICES + DEFAULT_STYLE = settings.CMSPLUGIN_FILER_IMAGE_DEFAULT_STYLE + EXCLUDED_KEYS = ['class', 'href', 'target', ] + + style = models.CharField( + _('Style'), choices=STYLE_CHOICES, default=DEFAULT_STYLE, max_length=50, blank=True) + caption_text = models.CharField(_("caption text"), null=True, blank=True, max_length=255) + image = FilerImageField( + null=True, + blank=True, + default=None, + verbose_name=_("image"), + on_delete=models.SET_NULL, + ) + image_url = models.URLField(_("alternative image url"), null=True, blank=True, default=None) + alt_text = models.CharField(_("alt text"), null=True, blank=True, max_length=255) + use_original_image = models.BooleanField( + _("use the original image"), default=False, + help_text=_('do not resize the image. use the original image instead.')) + thumbnail_option = models.ForeignKey( + 'filer.ThumbnailOption', null=True, blank=True, verbose_name=_("thumbnail option"), + help_text=_('overrides width, height, crop and upscale with values from the selected thumbnail option'), on_delete=models.CASCADE) + use_autoscale = models.BooleanField(_("use automatic scaling"), default=False, + help_text=_('tries to auto scale the image based on the placeholder context')) + width = models.PositiveIntegerField(_("width"), null=True, blank=True) + height = models.PositiveIntegerField(_("height"), null=True, blank=True) + crop = models.BooleanField(_("crop"), default=True) + upscale = models.BooleanField(_("upscale"), default=True) + alignment = models.CharField(_("image alignment"), max_length=10, blank=True, null=True, choices=FLOAT_CHOICES) + + free_link = models.CharField(_("link"), max_length=2000, blank=True, null=True, + help_text=_("if present image will be clickable")) + page_link = PageField(null=True, blank=True, + help_text=_("if present image will be clickable"), + verbose_name=_("page link")) + file_link = FilerFileField( + null=True, + blank=True, + default=None, + verbose_name=_("file link"), + help_text=_("if present image will be clickable"), + related_name='+', + on_delete=models.SET_NULL, + ) + original_link = models.BooleanField(_("link original image"), default=False, + help_text=_("if present image will be clickable")) + description = models.TextField(_("description"), blank=True, null=True) + target_blank = models.BooleanField(_('Open link in new window'), default=False) + link_attributes = AttributesField(excluded_keys=EXCLUDED_KEYS, blank=True, + help_text=_('Optional. Adds HTML attributes to the rendered link.')) + cmsplugin_ptr = models.OneToOneField( + to=CMSPlugin, + related_name='%(app_label)s_%(class)s', + parent_link=True, + on_delete=models.CASCADE + ) + + # we only add the image to select_related. page_link and file_link are FKs + # as well, but they are not used often enough to warrant the impact of two + # additional LEFT OUTER JOINs. + objects = FilerPluginManager(select_related=('image',)) + + class Meta: + verbose_name = _("filer image") + verbose_name_plural = _("filer images") + + def clean(self): + from django.core.exceptions import ValidationError + # Make sure that either image or image_url is set + if (not self.image and not self.image_url) or (self.image and self.image_url): + raise ValidationError(_('Either an image or an image url must be selected.')) + + def __str__(self): + if self.image: + return self.image.label + else: + return _("Image Publication %(caption)s") % {'caption': self.caption or self.alt} + return '' + + @property + def caption(self): + if self.image: + return self.caption_text or self.image.default_caption + else: + return self.caption_text + + @property + def alt(self): + if self.image: + return self.alt_text or self.image.default_alt_text or self.image.label + else: + return self.alt_text + + @property + def link(self): + if self.free_link: + return self.free_link + elif self.page_link: + return self.page_link.get_absolute_url() + elif self.file_link: + return self.file_link.url + elif self.original_link: + if self.image: + return self.image.url + else: + return self.image_url + else: + return '' diff --git a/cmsplugin_filer_image/templates/cmsplugin_filer_image/plugins/image/default.html b/cmsplugin_filer_image/templates/cmsplugin_filer_image/plugins/image/default.html new file mode 100644 index 00000000..798e3dcb --- /dev/null +++ b/cmsplugin_filer_image/templates/cmsplugin_filer_image/plugins/image/default.html @@ -0,0 +1,35 @@ +{% load thumbnail filer_tags filer_image_tags %}{% spaceless %} +{% comment %} + You may change the image size for special cases in your project by overriding + this template. There are a few size manipulation filters for this in + `filer_image_tags`: + + {% if placeholder == 'my_special_sidebar' %} + {% thumbnail instance.image opts.size|extra_padding_y:10 crop=opts.crop upscale=opts.upscale as thumbnail %} + {% else %} + {% thumbnail instance.image opts.size crop=opts.crop upscale=opts.upscale as thumbnail %} + {% endif %} + {% if link %}{% endif %}{% if link %}{% endif %} +{% endcomment %} + +{% if link %}{% endif %} +{% if instance.image %} + {% if instance.use_original_image %} + {% if instance.alt %}{{ instance.alt }}{% endif %} + {% else %} + {% thumbnail instance.image size crop=opts.crop upscale=opts.upscale subject_location=opts.subject_location as thumbnail %} + {% if instance.alt %}{{ instance.alt }}{% endif %} + {% endif %} +{% else %} + {# just a plain link to some external image #} + {% if instance.alt %}{{ instance.alt }}{% endif %} +{% endif %} +{% if instance.caption or instance.description %} + + {% if instance.caption %}{{ instance.caption }}{% endif %} + {% if instance.description %}{{ instance.description }}{% endif %} + +{% endif %} +{% if link %}{% endif %} + +{% endspaceless %} diff --git a/cmsplugin_filer_link/__init__.py b/cmsplugin_filer_link/__init__.py new file mode 100644 index 00000000..10a7a433 --- /dev/null +++ b/cmsplugin_filer_link/__init__.py @@ -0,0 +1 @@ +from cmsplugin_filer_file import __version__ # NOQA diff --git a/cmsplugin_filer_link/cms_plugins.py b/cmsplugin_filer_link/cms_plugins.py new file mode 100644 index 00000000..bd88cb47 --- /dev/null +++ b/cmsplugin_filer_link/cms_plugins.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.templatetags.static import static +from django.utils.translation import ugettext as _ + +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool + +from .forms import FilerLinkForm +from .models import FilerLinkPlugin as FilerLinkPluginModel + + +class FilerLinkPlugin(CMSPluginBase): + form = FilerLinkForm + model = FilerLinkPluginModel + module = 'Filer' + name = _("Link") + raw_id_fields = ('page_link', ) + render_template = "cmsplugin_filer_link/link.html" + text_enabled = True + + fieldsets = ( + (None, { + 'fields': [ + 'name', + 'url', + 'page_link', + 'mailto', + 'file', + 'link_style', + 'new_window', + ] + }), + (_('Advanced'), { + 'classes': ['collapse', ], + 'fields': [ + 'link_attributes', + ] + }) + ) + + def render(self, context, instance, placeholder): + context = super(FilerLinkPlugin, self).render(context, instance, placeholder) + if instance.file: + link = instance.file.url + elif instance.mailto: + link = "mailto:%s" % _(instance.mailto) + elif instance.url: + link = _(instance.url) + elif instance.page_link: + link = instance.page_link.get_absolute_url() + else: + link = "" + context.update({ + 'link': link, + 'style': instance.link_style, + 'name': instance.name, + 'new_window': instance.new_window, + }) + return context + + def icon_src(self, instance): + return static("cms/img/icons/plugins/link.png") + + +plugin_pool.register_plugin(FilerLinkPlugin) diff --git a/cmsplugin_filer_link/forms.py b/cmsplugin_filer_link/forms.py new file mode 100644 index 00000000..bac8adf8 --- /dev/null +++ b/cmsplugin_filer_link/forms.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django import forms + +from djangocms_attributes_field.widgets import AttributesWidget + +from .models import FilerLinkPlugin + + +class FilerLinkForm(forms.ModelForm): + + class Meta: + model = FilerLinkPlugin + exclude = [] + + def __init__(self, *args, **kwargs): + super(FilerLinkForm, self).__init__(*args, **kwargs) + self.fields['link_attributes'].widget = AttributesWidget() diff --git a/cmsplugin_filer_link/locale/ca/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/ca/LC_MESSAGES/django.po new file mode 100644 index 00000000..61fff0ad --- /dev/null +++ b/cmsplugin_filer_link/locale/ca/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Roger Pons , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Catalan (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Enllaç" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "nom" + +#: models.py:29 +msgid "url" +msgstr "url" + +#: models.py:31 +msgid "page" +msgstr "pàgina" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Un enllaç a pàgina té prioritat sobre urls" + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Una adreça d'email té prioritat sobre pàgines i urls" + +#: models.py:39 +msgid "link style" +msgstr "estil d'enllaç" + +#: models.py:41 +msgid "new window?" +msgstr "nova finestra?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Voleu que aquest enllaç obrir una nova finestra?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/de/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/de/LC_MESSAGES/django.po new file mode 100644 index 00000000..644d5c12 --- /dev/null +++ b/cmsplugin_filer_link/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,68 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Jannik V. , 2016 +# Michal Bielicki , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: German (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Link" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "" + +#: models.py:29 +msgid "url" +msgstr "URL" + +#: models.py:31 +msgid "page" +msgstr "Seite" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "" + +#: models.py:41 +msgid "new window?" +msgstr "neues Fenster ?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Soll dieser Link in einem neuen Fenster geöffnet werden?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/en/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..a5f77fe4 --- /dev/null +++ b/cmsplugin_filer_link/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "" + +#: models.py:29 +msgid "url" +msgstr "" + +#: models.py:31 +msgid "page" +msgstr "" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "" + +#: models.py:41 +msgid "new window?" +msgstr "" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/es/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..59e3c6bd --- /dev/null +++ b/cmsplugin_filer_link/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Javier Sánchez , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Spanish (http://www.transifex.com/divio/cmsplugin-filer/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Enlace" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "nombre" + +#: models.py:29 +msgid "url" +msgstr "url" + +#: models.py:31 +msgid "page" +msgstr "página" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Un enlace a la página tiene prioridad sobre las urls." + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Una dirección de correo tiene prioridad sobre ambas páginas y urls" + +#: models.py:39 +msgid "link style" +msgstr "estilo de enlace" + +#: models.py:41 +msgid "new window?" +msgstr "¿nueva ventana?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "¿Quieres que este enlace se abra en una nueva ventana?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/et/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/et/LC_MESSAGES/django.po new file mode 100644 index 00000000..c6fd2a52 --- /dev/null +++ b/cmsplugin_filer_link/locale/et/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rivo Zängov , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Estonian (http://www.transifex.com/divio/cmsplugin-filer/language/et/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Link" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "nimi" + +#: models.py:29 +msgid "url" +msgstr "url" + +#: models.py:31 +msgid "page" +msgstr "leht" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "lingi stiil" + +#: models.py:41 +msgid "new window?" +msgstr "uues aknas?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Kas sa soovid seda linki avada uues aknas?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/eu/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/eu/LC_MESSAGES/django.po new file mode 100644 index 00000000..f9ef8899 --- /dev/null +++ b/cmsplugin_filer_link/locale/eu/LC_MESSAGES/django.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Basque (http://www.transifex.com/divio/cmsplugin-filer/language/eu/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: eu\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Esteka" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "izena" + +#: models.py:29 +msgid "url" +msgstr "helbidea" + +#: models.py:31 +msgid "page" +msgstr "orria" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Orri baterako estekak helbide baterako estekak baino lehentasun handiagoa du" + +#: models.py:37 +msgid "mailto" +msgstr "e-posta" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "E-posta helbide batek orri edo helbidea baino lehentasun handiagoa du" + +#: models.py:39 +msgid "link style" +msgstr "esteka estiloa" + +#: models.py:41 +msgid "new window?" +msgstr "leiho berria?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Esteka hau leiho berri batean irekitzea nahi duzu?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/fi/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/fi/LC_MESSAGES/django.po new file mode 100644 index 00000000..61d68f9c --- /dev/null +++ b/cmsplugin_filer_link/locale/fi/LC_MESSAGES/django.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Finnish (http://www.transifex.com/divio/cmsplugin-filer/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "" + +#: models.py:29 +msgid "url" +msgstr "" + +#: models.py:31 +msgid "page" +msgstr "" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "" + +#: models.py:41 +msgid "new window?" +msgstr "" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/fr/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..912d6c68 --- /dev/null +++ b/cmsplugin_filer_link/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,68 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Bertrand Bordage , 2013 +# Frigory33 , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-06 18:35+0000\n" +"Last-Translator: Frigory33 \n" +"Language-Team: French (http://www.transifex.com/divio/cmsplugin-filer/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Lien" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "Avancé" + +#: models.py:28 +msgid "name" +msgstr "nom" + +#: models.py:29 +msgid "url" +msgstr "url" + +#: models.py:31 +msgid "page" +msgstr "page" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Un lien vers une page est prioritaire sur une URL." + +#: models.py:37 +msgid "mailto" +msgstr "courriel" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Un lien vers un courriel est prioritaire sur un lien vers une page ou une URL." + +#: models.py:39 +msgid "link style" +msgstr "style de lien" + +#: models.py:41 +msgid "new window?" +msgstr "nouvelle fenêtre ?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Voulez-vous ouvrir ce lien dans une nouvelle fenêtre ?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Facultatif. Ajoute des attributs HTML au lien généré." diff --git a/cmsplugin_filer_link/locale/it/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/it/LC_MESSAGES/django.po new file mode 100644 index 00000000..14a8958e --- /dev/null +++ b/cmsplugin_filer_link/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# yakky , 2013,2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-13 08:28+0000\n" +"Last-Translator: yakky \n" +"Language-Team: Italian (http://www.transifex.com/divio/cmsplugin-filer/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Link" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "Avanzate" + +#: models.py:28 +msgid "name" +msgstr "Nome" + +#: models.py:29 +msgid "url" +msgstr "URL" + +#: models.py:31 +msgid "page" +msgstr "pagina" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Un link ad una pagina ha la priorità rispetto agli URL." + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Un indirizzo email ha la priorità rispetto alle pagina e agli URL." + +#: models.py:39 +msgid "link style" +msgstr "stile link" + +#: models.py:41 +msgid "new window?" +msgstr "nuova finestra?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Vuoi che questo link si apra in una nuova finestra?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Opzionale. Aggiungi attributi HTML al link" diff --git a/cmsplugin_filer_link/locale/it_IT/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/it_IT/LC_MESSAGES/django.po new file mode 100644 index 00000000..b04db853 --- /dev/null +++ b/cmsplugin_filer_link/locale/it_IT/LC_MESSAGES/django.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Italian (Italy) (http://www.transifex.com/divio/cmsplugin-filer/language/it_IT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it_IT\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "" + +#: models.py:29 +msgid "url" +msgstr "" + +#: models.py:31 +msgid "page" +msgstr "" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "" + +#: models.py:41 +msgid "new window?" +msgstr "" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/lt/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/lt/LC_MESSAGES/django.po new file mode 100644 index 00000000..4b607cac --- /dev/null +++ b/cmsplugin_filer_link/locale/lt/LC_MESSAGES/django.po @@ -0,0 +1,68 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Matas Dailyda, 2015 +# Matas Dailyda , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-07-18 09:19+0000\n" +"Last-Translator: Matas Dailyda \n" +"Language-Team: Lithuanian (http://www.transifex.com/divio/cmsplugin-filer/language/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Nuoroda" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "Išplėstiniai" + +#: models.py:28 +msgid "name" +msgstr "pavadinimas" + +#: models.py:29 +msgid "url" +msgstr "nuoroda" + +#: models.py:31 +msgid "page" +msgstr "puslapis" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Nuoroda į puslapį turi didesnį prioritetą nei tekstinė nuoroda." + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Elektroninio pašto nuoroda turi didesnį prioritetą nei tekstinė nuoroda." + +#: models.py:39 +msgid "link style" +msgstr "nuorodos stilius" + +#: models.py:41 +msgid "new window?" +msgstr "naujas langas?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Ar norite kad ši nuoroda atidarytų naują langą?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Nebūtina. Prideda HTML atributus prie atvaizduojamos nuorodos." diff --git a/cmsplugin_filer_link/locale/lt_LT/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/lt_LT/LC_MESSAGES/django.po new file mode 100644 index 00000000..9457f719 --- /dev/null +++ b/cmsplugin_filer_link/locale/lt_LT/LC_MESSAGES/django.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Lithuanian (Lithuania) (http://www.transifex.com/divio/cmsplugin-filer/language/lt_LT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt_LT\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "" + +#: models.py:29 +msgid "url" +msgstr "" + +#: models.py:31 +msgid "page" +msgstr "" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "" + +#: models.py:41 +msgid "new window?" +msgstr "" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/nb/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/nb/LC_MESSAGES/django.po new file mode 100644 index 00000000..08496458 --- /dev/null +++ b/cmsplugin_filer_link/locale/nb/LC_MESSAGES/django.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Bokmål (http://www.transifex.com/divio/cmsplugin-filer/language/nb/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nb\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "" + +#: models.py:29 +msgid "url" +msgstr "" + +#: models.py:31 +msgid "page" +msgstr "" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "" + +#: models.py:41 +msgid "new window?" +msgstr "" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/nl/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/nl/LC_MESSAGES/django.po new file mode 100644 index 00000000..10a44d4c --- /dev/null +++ b/cmsplugin_filer_link/locale/nl/LC_MESSAGES/django.po @@ -0,0 +1,69 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Maarten Draijer , 2013 +# Maikel Wever, 2013 +# kurdyma , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-27 20:24+0000\n" +"Last-Translator: kurdyma \n" +"Language-Team: Dutch (http://www.transifex.com/divio/cmsplugin-filer/language/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Link" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "Geavanveerd" + +#: models.py:28 +msgid "name" +msgstr "naam" + +#: models.py:29 +msgid "url" +msgstr "url" + +#: models.py:31 +msgid "page" +msgstr "pagina" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Een link naar een pagina heeft meer prioriteit dan urls." + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Een e-mailadres heeft meer prioriteit dan pagina's en urls" + +#: models.py:39 +msgid "link style" +msgstr "link stijl" + +#: models.py:41 +msgid "new window?" +msgstr "nieuw venster?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Wil je deze link laten openen in een nieuw venster?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "Optioneel. Voegt HTML attributen toe aan de gerenderde link." diff --git a/cmsplugin_filer_link/locale/nn/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/nn/LC_MESSAGES/django.po new file mode 100644 index 00000000..089d5481 --- /dev/null +++ b/cmsplugin_filer_link/locale/nn/LC_MESSAGES/django.po @@ -0,0 +1,66 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Norwegian Nynorsk (http://www.transifex.com/divio/cmsplugin-filer/language/nn/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nn\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "" + +#: models.py:29 +msgid "url" +msgstr "" + +#: models.py:31 +msgid "page" +msgstr "" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "" + +#: models.py:37 +msgid "mailto" +msgstr "" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "" + +#: models.py:39 +msgid "link style" +msgstr "" + +#: models.py:41 +msgid "new window?" +msgstr "" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/pl_PL/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/pl_PL/LC_MESSAGES/django.po new file mode 100644 index 00000000..67c1cc55 --- /dev/null +++ b/cmsplugin_filer_link/locale/pl_PL/LC_MESSAGES/django.po @@ -0,0 +1,68 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Mateusz Marzantowicz , 2013 +# Michal Bielicki , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/divio/cmsplugin-filer/language/pl_PL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Link" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "nazwa" + +#: models.py:29 +msgid "url" +msgstr "URL" + +#: models.py:31 +msgid "page" +msgstr "strona" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Odnośnik do strony ma priorytet nad adresami URL." + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Adres e-mail ma priorytet nad odnośnikami do stron oraz nad adresami URL" + +#: models.py:39 +msgid "link style" +msgstr "styl odnośnika" + +#: models.py:41 +msgid "new window?" +msgstr "nowe okno ?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Czy chciałbyś otworzyć ten link w nowym okienku ?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/pt_BR/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 00000000..e4dcacb5 --- /dev/null +++ b/cmsplugin_filer_link/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rodrigo , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/divio/cmsplugin-filer/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Link" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "nome" + +#: models.py:29 +msgid "url" +msgstr "url" + +#: models.py:31 +msgid "page" +msgstr "página" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Um link para uma página tem prioridade sobre as urls." + +#: models.py:37 +msgid "mailto" +msgstr "mailto" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Um endereço de e-mail tem prioridade sobre urls e páginas" + +#: models.py:39 +msgid "link style" +msgstr "link style" + +#: models.py:41 +msgid "new window?" +msgstr "nova janela?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Você deseja que este link seja aberto em uma nova janela?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/ru/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 00000000..e988fd31 --- /dev/null +++ b/cmsplugin_filer_link/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Pavel , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Russian (http://www.transifex.com/divio/cmsplugin-filer/language/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "Ссылка" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "имя" + +#: models.py:29 +msgid "url" +msgstr "URL" + +#: models.py:31 +msgid "page" +msgstr "страница" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "Ссылка на страницу имеет больший приоритет" + +#: models.py:37 +msgid "mailto" +msgstr "почта" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "Ссылка на почту имеет больший приоритет над ссылкой на страницу или URL" + +#: models.py:39 +msgid "link style" +msgstr "стиль ссылки" + +#: models.py:41 +msgid "new window?" +msgstr "в новом окне?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "Необходимо открыть ссылку в новом окне?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/locale/zh_CN/LC_MESSAGES/django.po b/cmsplugin_filer_link/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 00000000..566d6158 --- /dev/null +++ b/cmsplugin_filer_link/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Calvin-He , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-06-22 17:49+0000\n" +"Last-Translator: Martin Koistinen \n" +"Language-Team: Chinese (China) (http://www.transifex.com/divio/cmsplugin-filer/language/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: cms_plugins.py:18 +msgid "Link" +msgstr "链接" + +#: cms_plugins.py:35 +msgid "Advanced" +msgstr "" + +#: models.py:28 +msgid "name" +msgstr "名称" + +#: models.py:29 +msgid "url" +msgstr "" + +#: models.py:31 +msgid "page" +msgstr "页面" + +#: models.py:34 +msgid "A link to a page has priority over urls." +msgstr "到页面的链接优先于urls" + +#: models.py:37 +msgid "mailto" +msgstr "发送到" + +#: models.py:38 +msgid "An email address has priority over both pages and urls" +msgstr "邮箱地址优先于页面和urls" + +#: models.py:39 +msgid "link style" +msgstr "链接样式" + +#: models.py:41 +msgid "new window?" +msgstr "新窗口?" + +#: models.py:42 +msgid "Do you want this link to open a new window?" +msgstr "你想要在新窗口打开这个链接吗?" + +#: models.py:45 +msgid "Optional. Adds HTML attributes to the rendered link." +msgstr "" diff --git a/cmsplugin_filer_link/migrations/0001_initial.py b/cmsplugin_filer_link/migrations/0001_initial.py new file mode 100644 index 00000000..055edce7 --- /dev/null +++ b/cmsplugin_filer_link/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import filer.fields.file +import cms.models.fields +from cmsplugin_filer_link.models import LINK_STYLES + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0003_auto_20140926_2347'), + ('filer', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='FilerLinkPlugin', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE)), + ('name', models.CharField(max_length=255, verbose_name='name')), + ('url', models.CharField(max_length=255, null=True, verbose_name='url', blank=True)), + ('mailto', models.EmailField(help_text='An email address has priority over both pages and urls', max_length=75, null=True, verbose_name='mailto', blank=True)), + ('link_style', models.CharField(max_length=255, verbose_name='link style', default=LINK_STYLES[0][0], choices=LINK_STYLES)), + ('new_window', models.BooleanField(default=False, help_text='Do you want this link to open a new window?', verbose_name='new window?')), + ('file', filer.fields.file.FilerFileField(blank=True, to='filer.File', null=True, on_delete=models.CASCADE)), + ('page_link', cms.models.fields.PageField(blank=True, to='cms.Page', help_text='A link to a page has priority over urls.', null=True, verbose_name='page')), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/cmsplugin_filer_link/migrations/0002_auto_20160108_1710.py b/cmsplugin_filer_link/migrations/0002_auto_20160108_1710.py new file mode 100644 index 00000000..97126b98 --- /dev/null +++ b/cmsplugin_filer_link/migrations/0002_auto_20160108_1710.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import filer.fields.file +import django.db.models.deletion +import cms.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_link', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='filerlinkplugin', + name='file', + field=filer.fields.file.FilerFileField(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='filer.File', null=True), + ), + migrations.AlterField( + model_name='filerlinkplugin', + name='page_link', + field=cms.models.fields.PageField(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='cms.Page', help_text='A link to a page has priority over urls.', null=True, verbose_name='page'), + ), + ] diff --git a/cmsplugin_filer_link/migrations/0003_auto_20160224_1453.py b/cmsplugin_filer_link/migrations/0003_auto_20160224_1453.py new file mode 100644 index 00000000..372d2fdf --- /dev/null +++ b/cmsplugin_filer_link/migrations/0003_auto_20160224_1453.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_link', '0002_auto_20160108_1710'), + ] + + operations = [ + migrations.AlterField( + model_name='filerlinkplugin', + name='mailto', + field=models.EmailField(help_text='An email address has priority over both pages and urls', max_length=254, null=True, verbose_name='mailto', blank=True), + ), + ] diff --git a/cmsplugin_filer_link/migrations/0004_auto_20160224_1457.py b/cmsplugin_filer_link/migrations/0004_auto_20160224_1457.py new file mode 100644 index 00000000..43126dab --- /dev/null +++ b/cmsplugin_filer_link/migrations/0004_auto_20160224_1457.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_link', '0003_auto_20160224_1453'), + ] + + operations = [ + migrations.AlterField( + model_name='filerlinkplugin', + name='url', + field=models.CharField(max_length=2000, null=True, verbose_name='url', blank=True), + ), + ] diff --git a/cmsplugin_filer_link/migrations/0005_filerlinkplugin_link_attributes.py b/cmsplugin_filer_link/migrations/0005_filerlinkplugin_link_attributes.py new file mode 100644 index 00000000..1346a29d --- /dev/null +++ b/cmsplugin_filer_link/migrations/0005_filerlinkplugin_link_attributes.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_link', '0004_auto_20160224_1457'), + ] + + operations = [ + migrations.AddField( + model_name='filerlinkplugin', + name='link_attributes', + field=djangocms_attributes_field.fields.AttributesField(default=dict, help_text='Optional. Adds HTML attributes to the rendered link.'), + ), + ] diff --git a/cmsplugin_filer_link/migrations/0006_auto_20160705_1334.py b/cmsplugin_filer_link/migrations/0006_auto_20160705_1334.py new file mode 100644 index 00000000..5f0f1d6a --- /dev/null +++ b/cmsplugin_filer_link/migrations/0006_auto_20160705_1334.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-07-05 17:34 +from __future__ import unicode_literals + +from django.db import migrations +import djangocms_attributes_field.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_link', '0005_filerlinkplugin_link_attributes'), + ] + + operations = [ + migrations.AlterField( + model_name='filerlinkplugin', + name='link_attributes', + field=djangocms_attributes_field.fields.AttributesField(blank=True, default=dict, help_text='Optional. Adds HTML attributes to the rendered link.'), + ), + ] diff --git a/cmsplugin_filer_link/migrations/0007_auto_20160713_1853.py b/cmsplugin_filer_link/migrations/0007_auto_20160713_1853.py new file mode 100644 index 00000000..5ed8cb10 --- /dev/null +++ b/cmsplugin_filer_link/migrations/0007_auto_20160713_1853.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_link', '0006_auto_20160705_1334'), + ] + + operations = [ + migrations.AlterField( + model_name='filerlinkplugin', + name='cmsplugin_ptr', + field=models.OneToOneField(parent_link=True, related_name='cmsplugin_filer_link_filerlinkplugin', primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE), + ), + ] diff --git a/cmsplugin_filer_link/migrations/__init__.py b/cmsplugin_filer_link/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cmsplugin_filer_link/models.py b/cmsplugin_filer_link/models.py new file mode 100644 index 00000000..bb0c73ac --- /dev/null +++ b/cmsplugin_filer_link/models.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from django.conf import settings + +from cms.models import CMSPlugin +from cms.models.fields import PageField + +from filer.fields.file import FilerFileField + +from djangocms_attributes_field.fields import AttributesField + + +DEFULT_LINK_STYLES = ( + (" ", "Default"), +) + +LINK_STYLES = getattr(settings, "FILER_LINK_STYLES", DEFULT_LINK_STYLES) + +EXCLUDED_KEYS = ['class', 'href', 'target', ] + + +class FilerLinkPlugin(CMSPlugin): + name = models.CharField(_('name'), max_length=255) + url = models.CharField(_("url"), blank=True, null=True, max_length=2000) + page_link = PageField( + verbose_name=_("page"), + blank=True, + null=True, + help_text=_("A link to a page has priority over urls."), + on_delete=models.SET_NULL, + ) + mailto = models.EmailField(_("mailto"), blank=True, null=True, max_length=254, + help_text=_("An email address has priority over both pages and urls")) + link_style = models.CharField(_("link style"), max_length=255, + choices=LINK_STYLES, default=LINK_STYLES[0][0]) + new_window = models.BooleanField(_("new window?"), default=False, + help_text=_("Do you want this link to open a new window?")) + file = FilerFileField(blank=True, null=True, on_delete=models.SET_NULL) + link_attributes = AttributesField(excluded_keys=EXCLUDED_KEYS, blank=True, + help_text=_('Optional. Adds HTML attributes to the rendered link.')) + cmsplugin_ptr = models.OneToOneField( + to=CMSPlugin, + related_name='%(app_label)s_%(class)s', + parent_link=True, + on_delete=models.CASCADE + ) + + def __str__(self): + return self.name diff --git a/cmsplugin_filer_link/templates/cmsplugin_filer_link/link.html b/cmsplugin_filer_link/templates/cmsplugin_filer_link/link.html new file mode 100644 index 00000000..2e046586 --- /dev/null +++ b/cmsplugin_filer_link/templates/cmsplugin_filer_link/link.html @@ -0,0 +1 @@ +{{ name }} \ No newline at end of file diff --git a/cmsplugin_filer_utils/__init__.py b/cmsplugin_filer_utils/__init__.py new file mode 100644 index 00000000..a52e69e6 --- /dev/null +++ b/cmsplugin_filer_utils/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from cmsplugin_filer_file import __version__ # NOQA + +from django.db import models + + +class FilerPluginManager(models.Manager): + def __init__(self, select_related=None): + self._select_related = select_related + super(FilerPluginManager, self).__init__() + + def get_queryset(self): + # TODO: Remove once support for Django 1.4/1.5 are dropped + try: + qs = super(FilerPluginManager, self).get_queryset() + except AttributeError: + qs = super(FilerPluginManager, self).get_query_set() + if self._select_related: + qs = qs.prefetch_related(*self._select_related) + return qs + + get_query_set = get_queryset diff --git a/cmsplugin_filer_utils/migration.py b/cmsplugin_filer_utils/migration.py new file mode 100644 index 00000000..6f1dd81c --- /dev/null +++ b/cmsplugin_filer_utils/migration.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + + +def rename_tables(db, table_mapping, reverse=False): + """ + renames tables from source to destination name, if the source exists and the destination does + not exist yet. + """ + from django.db import connection + if reverse: + table_mapping = [(dst, src) for src, dst in table_mapping] + table_names = connection.introspection.table_names() + for source, destination in table_mapping: + if source in table_names and destination in table_names: + print(u" WARNING: not renaming {0} to {1}, because both tables already exist.".format(source, destination)) + elif source in table_names and destination not in table_names: + print(u" - renaming {0} to {1}".format(source, destination)) + db.rename_table(source, destination) + + +def rename_tables_old_to_new(db, table_mapping): + return rename_tables(db, table_mapping, reverse=False) + + +def rename_tables_new_to_old(db, table_mapping): + return rename_tables(db, table_mapping, reverse=True) diff --git a/cmsplugin_filer_video/__init__.py b/cmsplugin_filer_video/__init__.py new file mode 100644 index 00000000..10a7a433 --- /dev/null +++ b/cmsplugin_filer_video/__init__.py @@ -0,0 +1 @@ +from cmsplugin_filer_file import __version__ # NOQA diff --git a/cmsplugin_filer_video/cms_plugins.py b/cmsplugin_filer_video/cms_plugins.py new file mode 100644 index 00000000..ba6102c9 --- /dev/null +++ b/cmsplugin_filer_video/cms_plugins.py @@ -0,0 +1,62 @@ +from __future__ import unicode_literals + +from cms.plugin_pool import plugin_pool +from cms.plugin_base import CMSPluginBase +from django.templatetags.static import static +from django.utils.translation import ugettext_lazy as _ +from cmsplugin_filer_video import settings +from cmsplugin_filer_video.models import FilerVideo +from cmsplugin_filer_video.forms import VideoForm + + +class FilerVideoPlugin(CMSPluginBase): + module = 'Filer' + model = FilerVideo + name = _("Video") + form = VideoForm + + render_template = "cmsplugin_filer_video/video.html" + text_enabled = True + + general_fields = [ + 'movie', + 'movie_url', + 'image', + ('width', 'height'), + 'auto_play', + 'auto_hide', + 'fullscreen', + 'loop', + ] + color_fields = [ + 'bgcolor', + 'textcolor', + 'seekbarcolor', + 'seekbarbgcolor', + 'loadingbarcolor', + 'buttonoutcolor', + 'buttonovercolor', + 'buttonhighlightcolor', + ] + + fieldsets = [ + (None, { + 'fields': general_fields, + }), + ] + if settings.VIDEO_PLUGIN_ENABLE_ADVANCED_SETTINGS: + fieldsets += [ + (_('Color Settings'), { + 'fields': color_fields, + 'classes': ('collapse',), + }), + ] + + def render(self, context, instance, placeholder): + context['object'] = instance + return super(FilerVideoPlugin, self).render(context, instance, placeholder) + + def icon_src(self, instance): + return static("filer/icons/video_%sx%s.png" % (32, 32,)) + +plugin_pool.register_plugin(FilerVideoPlugin) diff --git a/cmsplugin_filer_video/forms.py b/cmsplugin_filer_video/forms.py new file mode 100644 index 00000000..ae350fd5 --- /dev/null +++ b/cmsplugin_filer_video/forms.py @@ -0,0 +1,9 @@ +from django import forms +from cmsplugin_filer_video.models import FilerVideo + + +class VideoForm(forms.ModelForm): + + class Meta: + model = FilerVideo + exclude = ('page', 'position', 'placeholder', 'language', 'plugin_type') diff --git a/cmsplugin_filer_video/locale/ca/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/ca/LC_MESSAGES/django.po new file mode 100644 index 00000000..5180a650 --- /dev/null +++ b/cmsplugin_filer_video/locale/ca/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Roger Pons , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Catalan (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Video" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Configuració del color" + +#: models.py:17 +msgid "movie file" +msgstr "fitxer de pel·lícula" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "utilitza un fitxer .flv o codificat en h264" + +#: models.py:23 +msgid "movie url" +msgstr "url de la pel·lícula" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "url de vimeo o youtube. Exemple: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "imatge" + +#: models.py:26 +msgid "preview image file" +msgstr "previsualitza fitxer d'imatge" + +#: models.py:33 +msgid "width" +msgstr "amplada" + +#: models.py:34 +msgid "height" +msgstr "alçada" + +#: models.py:36 +msgid "auto play" +msgstr "inici automàtic" + +#: models.py:37 +msgid "auto hide" +msgstr "amaga automàticament" + +#: models.py:38 +msgid "fullscreen" +msgstr "pantalla completa" + +#: models.py:39 +msgid "loop" +msgstr "repeteix" + +#: models.py:42 +msgid "background color" +msgstr "color de fons" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Hexadecimal, exemple: ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "color de text" + +#: models.py:44 +msgid "seekbar color" +msgstr "color de la barra de cerca" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "color de fons de la barra de cerca" + +#: models.py:46 +msgid "loadingbar color" +msgstr "color de la barra de càrrega" + +#: models.py:47 +msgid "button out color" +msgstr "color de botó de fora" + +#: models.py:48 +msgid "button over color" +msgstr "color de botó per sobre" + +#: models.py:49 +msgid "button highlight color" +msgstr "color de botó resaltat" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "No es troba el plugin de flash. Descarregueu-lo aquí." diff --git a/cmsplugin_filer_video/locale/de/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/de/LC_MESSAGES/django.po new file mode 100644 index 00000000..116cf579 --- /dev/null +++ b/cmsplugin_filer_video/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Jannik V. , 2016 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-27 11:56+0000\n" +"Last-Translator: Jannik V. \n" +"Language-Team: German (http://www.transifex.com/divio/cmsplugin-filer/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" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Video" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Farbeinstellungen" + +#: models.py:17 +msgid "movie file" +msgstr "Videodatei" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr ".flv Datei oder h264 kodierte Videodatei benutzen" + +#: models.py:23 +msgid "movie url" +msgstr "Video-URL" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "vimeo oder youtube Video-URL. Beispiel: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "Bild" + +#: models.py:26 +msgid "preview image file" +msgstr "Vorschaubild" + +#: models.py:33 +msgid "width" +msgstr "Breite" + +#: models.py:34 +msgid "height" +msgstr "Höhe" + +#: models.py:36 +msgid "auto play" +msgstr "automatische Wiedergabe" + +#: models.py:37 +msgid "auto hide" +msgstr "" + +#: models.py:38 +msgid "fullscreen" +msgstr "Vollbild" + +#: models.py:39 +msgid "loop" +msgstr "" + +#: models.py:42 +msgid "background color" +msgstr "Hintergrundfarbe" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Hexadezimal, bspw. ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "Textfarbe" + +#: models.py:44 +msgid "seekbar color" +msgstr "" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "" + +#: models.py:46 +msgid "loadingbar color" +msgstr "" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Flash-Plugin fehlt. Download hier." diff --git a/cmsplugin_filer_video/locale/en/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/en/LC_MESSAGES/django.po new file mode 100644 index 00000000..26bd9b73 --- /dev/null +++ b/cmsplugin_filer_video/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "" + +#: models.py:17 +msgid "movie file" +msgstr "" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "" + +#: models.py:23 +msgid "movie url" +msgstr "" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "" + +#: models.py:25 +msgid "image" +msgstr "" + +#: models.py:26 +msgid "preview image file" +msgstr "" + +#: models.py:33 +msgid "width" +msgstr "" + +#: models.py:34 +msgid "height" +msgstr "" + +#: models.py:36 +msgid "auto play" +msgstr "" + +#: models.py:37 +msgid "auto hide" +msgstr "" + +#: models.py:38 +msgid "fullscreen" +msgstr "" + +#: models.py:39 +msgid "loop" +msgstr "" + +#: models.py:42 +msgid "background color" +msgstr "" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "" + +#: models.py:43 +msgid "text color" +msgstr "" + +#: models.py:44 +msgid "seekbar color" +msgstr "" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "" + +#: models.py:46 +msgid "loadingbar color" +msgstr "" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "" diff --git a/cmsplugin_filer_video/locale/es/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..eb41a5d7 --- /dev/null +++ b/cmsplugin_filer_video/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,121 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Javier Sánchez , 2015 +# David Díaz , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Spanish (http://www.transifex.com/divio/cmsplugin-filer/language/es/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Vídeo" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Configuración de Color" + +#: models.py:17 +msgid "movie file" +msgstr "archivo de vídeo" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "usar archivos de vídeo .flv o archivos vídeo codificados con h264" + +#: models.py:23 +msgid "movie url" +msgstr "url de película" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "url vimeo o youtube. Ejemplo: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "imagen" + +#: models.py:26 +msgid "preview image file" +msgstr "previsualización del archivo de imagen" + +#: models.py:33 +msgid "width" +msgstr "ancho" + +#: models.py:34 +msgid "height" +msgstr "alto" + +#: models.py:36 +msgid "auto play" +msgstr "auto reproducción" + +#: models.py:37 +msgid "auto hide" +msgstr "auto ocultar" + +#: models.py:38 +msgid "fullscreen" +msgstr "ventana completa" + +#: models.py:39 +msgid "loop" +msgstr "vuelta" + +#: models.py:42 +msgid "background color" +msgstr "color de fondo" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Hexadecimal, por ej. ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "color del texto" + +#: models.py:44 +msgid "seekbar color" +msgstr "color de la barra de progreso" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "color de fondo de la barra de progreso" + +#: models.py:46 +msgid "loadingbar color" +msgstr "color de la barra de carga" + +#: models.py:47 +msgid "button out color" +msgstr "color del botón fuera" + +#: models.py:48 +msgid "button over color" +msgstr "color del botón sobre" + +#: models.py:49 +msgid "button highlight color" +msgstr "color del botón activo" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Plugin de flash no encontrado. Descargar aquí." diff --git a/cmsplugin_filer_video/locale/et/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/et/LC_MESSAGES/django.po new file mode 100644 index 00000000..85c71908 --- /dev/null +++ b/cmsplugin_filer_video/locale/et/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rivo Zängov , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Estonian (http://www.transifex.com/divio/cmsplugin-filer/language/et/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: et\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Video" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Värvi seaded" + +#: models.py:17 +msgid "movie file" +msgstr "videofail" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "kasuta .flv faili või h264 kodeeritud videofaili" + +#: models.py:23 +msgid "movie url" +msgstr "video url" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "vimeo või youtube video url. Näiteks: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "pilt" + +#: models.py:26 +msgid "preview image file" +msgstr "pildifaili eelvaade" + +#: models.py:33 +msgid "width" +msgstr "laius" + +#: models.py:34 +msgid "height" +msgstr "kõrgus" + +#: models.py:36 +msgid "auto play" +msgstr "automaatne esitamine" + +#: models.py:37 +msgid "auto hide" +msgstr "automaatne peitmine" + +#: models.py:38 +msgid "fullscreen" +msgstr "täisekraan" + +#: models.py:39 +msgid "loop" +msgstr "kordamine" + +#: models.py:42 +msgid "background color" +msgstr "taustavärv" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "HEX-kood, nt ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "teksti värv" + +#: models.py:44 +msgid "seekbar color" +msgstr "otsimisriba värv" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "otsimisriba tausta värv" + +#: models.py:46 +msgid "loadingbar color" +msgstr "laadimisriba värv" + +#: models.py:47 +msgid "button out color" +msgstr "välja lülitatud nupu värv" + +#: models.py:48 +msgid "button over color" +msgstr "nupu värv hiire all" + +#: models.py:49 +msgid "button highlight color" +msgstr "esiletõstetud nupu värv" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Flashi plugin puudub. Lae alla siit." diff --git a/cmsplugin_filer_video/locale/eu/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/eu/LC_MESSAGES/django.po new file mode 100644 index 00000000..cadf56ac --- /dev/null +++ b/cmsplugin_filer_video/locale/eu/LC_MESSAGES/django.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Basque (http://www.transifex.com/divio/cmsplugin-filer/language/eu/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: eu\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Bideoa" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Kolore aukerak" + +#: models.py:17 +msgid "movie file" +msgstr "bideo fitxategia" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr ".flv edo h264 kodetzea erabiltzen duen fitxategia" + +#: models.py:23 +msgid "movie url" +msgstr "bideo helbidea" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "vimeo or youtube bideo helbidea . Adibidez: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "irudia" + +#: models.py:26 +msgid "preview image file" +msgstr "aurreikuspeneko irudi fitxategia" + +#: models.py:33 +msgid "width" +msgstr "zabalera" + +#: models.py:34 +msgid "height" +msgstr "altuera" + +#: models.py:36 +msgid "auto play" +msgstr "automatikoki erreproduzitu" + +#: models.py:37 +msgid "auto hide" +msgstr "automatikoki ezkutatu" + +#: models.py:38 +msgid "fullscreen" +msgstr "pantaila osoa" + +#: models.py:39 +msgid "loop" +msgstr "errepikatu" + +#: models.py:42 +msgid "background color" +msgstr "atzeko kolorea" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Hamaseitarra, adb. ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "testu kolorea" + +#: models.py:44 +msgid "seekbar color" +msgstr "nabigazio-barraren kolorea" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "nabigazio-barraren atzeko kolorea" + +#: models.py:46 +msgid "loadingbar color" +msgstr "karga-barraren kolorea" + +#: models.py:47 +msgid "button out color" +msgstr "botoiaren kolorea" + +#: models.py:48 +msgid "button over color" +msgstr "botoiaren gainjartze kolorea" + +#: models.py:49 +msgid "button highlight color" +msgstr "botoi nabarmenduaren kolorea" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Flash plugina falta da. hemendik deskargatu." diff --git a/cmsplugin_filer_video/locale/fi/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/fi/LC_MESSAGES/django.po new file mode 100644 index 00000000..22f86ab4 --- /dev/null +++ b/cmsplugin_filer_video/locale/fi/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Klaus Dahlén , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Finnish (http://www.transifex.com/divio/cmsplugin-filer/language/fi/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fi\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Video" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Väriasetukset" + +#: models.py:17 +msgid "movie file" +msgstr "videotiedosto" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "käytä .flv-tiedostoa tai h264-enkoodattua videotiedostoa" + +#: models.py:23 +msgid "movie url" +msgstr "videon URL" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "Vimeo- tai YouTube-videon URL. Esimerkiksi: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "kuva" + +#: models.py:26 +msgid "preview image file" +msgstr "kuvatiedoston esikatselu" + +#: models.py:33 +msgid "width" +msgstr "leveys" + +#: models.py:34 +msgid "height" +msgstr "korkeus" + +#: models.py:36 +msgid "auto play" +msgstr "automaattitoisto" + +#: models.py:37 +msgid "auto hide" +msgstr "automaattinen piilotus" + +#: models.py:38 +msgid "fullscreen" +msgstr "koko ruudun toisto" + +#: models.py:39 +msgid "loop" +msgstr "jatkuva toisto" + +#: models.py:42 +msgid "background color" +msgstr "taustaväri" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Heksadesimaali, esim. ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "tekstin väri" + +#: models.py:44 +msgid "seekbar color" +msgstr "toistopalkin väri" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "toistopalkin taustaväri" + +#: models.py:46 +msgid "loadingbar color" +msgstr "latauspalkin väri" + +#: models.py:47 +msgid "button out color" +msgstr "painikkeen väri" + +#: models.py:48 +msgid "button over color" +msgstr "painikkeen väri kun hiiri päällä" + +#: models.py:49 +msgid "button highlight color" +msgstr "painikkeen korostusväri" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Flash puuttuu. Lataa se tästä." diff --git a/cmsplugin_filer_video/locale/fr/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..65596505 --- /dev/null +++ b/cmsplugin_filer_video/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,121 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Bertrand Bordage , 2012 +# Bertrand Bordage , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: French (http://www.transifex.com/divio/cmsplugin-filer/language/fr/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Vidéo" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Réglages de couleur" + +#: models.py:17 +msgid "movie file" +msgstr "fichier vidéo" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "Utilisez un fichier vidéo encodé au format .FLV ou H264." + +#: models.py:23 +msgid "movie url" +msgstr "adresse de la vidéo" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "Adresse d'une vidéo vimeo ou youtube. Exemple : http://www.youtube.com/watch?v=ugqu10JV7dk" + +#: models.py:25 +msgid "image" +msgstr "image" + +#: models.py:26 +msgid "preview image file" +msgstr "fichier image de prévisualisation" + +#: models.py:33 +msgid "width" +msgstr "largeur" + +#: models.py:34 +msgid "height" +msgstr "hauteur" + +#: models.py:36 +msgid "auto play" +msgstr "lecture automatique" + +#: models.py:37 +msgid "auto hide" +msgstr "cacher automatiquement" + +#: models.py:38 +msgid "fullscreen" +msgstr "plein écran" + +#: models.py:39 +msgid "loop" +msgstr "boucle" + +#: models.py:42 +msgid "background color" +msgstr "couleur de fond" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Hexadécimal, par exemple FF00CC" + +#: models.py:43 +msgid "text color" +msgstr "couleur du texte" + +#: models.py:44 +msgid "seekbar color" +msgstr "couleur de la barre de déroulement" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "couleur de fond de la barre de déroulement" + +#: models.py:46 +msgid "loadingbar color" +msgstr "couleur de la barre de chargement" + +#: models.py:47 +msgid "button out color" +msgstr "couleur de bouton au repos" + +#: models.py:48 +msgid "button over color" +msgstr "couleur de bouton survolé" + +#: models.py:49 +msgid "button highlight color" +msgstr "couleur de bouton en surbrillance" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Plugin Flash manquant. Téléchargez-le ici." diff --git a/cmsplugin_filer_video/locale/it/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/it/LC_MESSAGES/django.po new file mode 100644 index 00000000..d2dd010a --- /dev/null +++ b/cmsplugin_filer_video/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,122 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# yakky , 2012 +# yakky , 2013 +# yakky , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Italian (http://www.transifex.com/divio/cmsplugin-filer/language/it/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Video" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Impostazioni dei colori" + +#: models.py:17 +msgid "movie file" +msgstr "File video" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "usa file un file video .flv o codificato h264" + +#: models.py:23 +msgid "movie url" +msgstr "URL video" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "URL video youtube. Example: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "immagine" + +#: models.py:26 +msgid "preview image file" +msgstr "immagine di anteprima" + +#: models.py:33 +msgid "width" +msgstr "larghezza" + +#: models.py:34 +msgid "height" +msgstr "altezza" + +#: models.py:36 +msgid "auto play" +msgstr "auto avvio" + +#: models.py:37 +msgid "auto hide" +msgstr "nascondi autom." + +#: models.py:38 +msgid "fullscreen" +msgstr "schermo pieno" + +#: models.py:39 +msgid "loop" +msgstr "loop" + +#: models.py:42 +msgid "background color" +msgstr "colore di sfondo" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Codice web, es ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "colore del testo" + +#: models.py:44 +msgid "seekbar color" +msgstr "colore della barra" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "colore di sfondo della barra" + +#: models.py:46 +msgid "loadingbar color" +msgstr "colore della barra di caricamento" + +#: models.py:47 +msgid "button out color" +msgstr "colore bottoni" + +#: models.py:48 +msgid "button over color" +msgstr "colore dei bottoni evidenziati" + +#: models.py:49 +msgid "button highlight color" +msgstr "colore dei bottoni selezionati" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Plugin flash mancante. E' possibile scaricarlo" diff --git a/cmsplugin_filer_video/locale/it_IT/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/it_IT/LC_MESSAGES/django.po new file mode 100644 index 00000000..585c473b --- /dev/null +++ b/cmsplugin_filer_video/locale/it_IT/LC_MESSAGES/django.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2012-07-13 16:08+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Italian (Italy) (http://www.transifex.com/divio/cmsplugin-filer/language/it_IT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: it_IT\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "" + +#: models.py:17 +msgid "movie file" +msgstr "" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "" + +#: models.py:23 +msgid "movie url" +msgstr "" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "" + +#: models.py:25 +msgid "image" +msgstr "" + +#: models.py:26 +msgid "preview image file" +msgstr "" + +#: models.py:33 +msgid "width" +msgstr "" + +#: models.py:34 +msgid "height" +msgstr "" + +#: models.py:36 +msgid "auto play" +msgstr "" + +#: models.py:37 +msgid "auto hide" +msgstr "" + +#: models.py:38 +msgid "fullscreen" +msgstr "" + +#: models.py:39 +msgid "loop" +msgstr "" + +#: models.py:42 +msgid "background color" +msgstr "" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "" + +#: models.py:43 +msgid "text color" +msgstr "" + +#: models.py:44 +msgid "seekbar color" +msgstr "" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "" + +#: models.py:46 +msgid "loadingbar color" +msgstr "" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "" diff --git a/cmsplugin_filer_video/locale/lt/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/lt/LC_MESSAGES/django.po new file mode 100644 index 00000000..7719c38c --- /dev/null +++ b/cmsplugin_filer_video/locale/lt/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Matas Dailyda, 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Lithuanian (http://www.transifex.com/divio/cmsplugin-filer/language/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Video" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Spalvos nustatymai" + +#: models.py:17 +msgid "movie file" +msgstr "vaizdo byla" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "naudoti .flv bylą arba h264 koduotą video bylą" + +#: models.py:23 +msgid "movie url" +msgstr "vaizdo nuoroda" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "vimeo arba youtube video nuoroda. Pavyzdžiui: http://www.youtube.com/watch?v=-iJ7bs4mTUY" + +#: models.py:25 +msgid "image" +msgstr "paveikslėlis" + +#: models.py:26 +msgid "preview image file" +msgstr "peržiūrėti paveikslėlį" + +#: models.py:33 +msgid "width" +msgstr "plotis" + +#: models.py:34 +msgid "height" +msgstr "aukštis" + +#: models.py:36 +msgid "auto play" +msgstr "automatinis grojimas" + +#: models.py:37 +msgid "auto hide" +msgstr "automatinis paslėpimas" + +#: models.py:38 +msgid "fullscreen" +msgstr "per visą ekraną" + +#: models.py:39 +msgid "loop" +msgstr "kartoti" + +#: models.py:42 +msgid "background color" +msgstr "fono spalva" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Šešioliktainis, pvz ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "teksto spalva" + +#: models.py:44 +msgid "seekbar color" +msgstr "laiko juostos spalva" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "laiko juostos fono spalva" + +#: models.py:46 +msgid "loadingbar color" +msgstr "krovimo juostos spalva" + +#: models.py:47 +msgid "button out color" +msgstr "mygtuko spalva atspaudus pelės klavišą" + +#: models.py:48 +msgid "button over color" +msgstr "mygtuko spalva užvilkus pelę" + +#: models.py:49 +msgid "button highlight color" +msgstr "mygtuko spalva paspaudus pelės klavišą" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Trūkstamas Flash įskiepas. Parsisiųsti čia." diff --git a/cmsplugin_filer_video/locale/lt_LT/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/lt_LT/LC_MESSAGES/django.po new file mode 100644 index 00000000..453cbef9 --- /dev/null +++ b/cmsplugin_filer_video/locale/lt_LT/LC_MESSAGES/django.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2012-07-13 16:08+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Lithuanian (Lithuania) (http://www.transifex.com/divio/cmsplugin-filer/language/lt_LT/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt_LT\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "" + +#: models.py:17 +msgid "movie file" +msgstr "" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "" + +#: models.py:23 +msgid "movie url" +msgstr "" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "" + +#: models.py:25 +msgid "image" +msgstr "" + +#: models.py:26 +msgid "preview image file" +msgstr "" + +#: models.py:33 +msgid "width" +msgstr "" + +#: models.py:34 +msgid "height" +msgstr "" + +#: models.py:36 +msgid "auto play" +msgstr "" + +#: models.py:37 +msgid "auto hide" +msgstr "" + +#: models.py:38 +msgid "fullscreen" +msgstr "" + +#: models.py:39 +msgid "loop" +msgstr "" + +#: models.py:42 +msgid "background color" +msgstr "" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "" + +#: models.py:43 +msgid "text color" +msgstr "" + +#: models.py:44 +msgid "seekbar color" +msgstr "" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "" + +#: models.py:46 +msgid "loadingbar color" +msgstr "" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "" diff --git a/cmsplugin_filer_video/locale/nb/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/nb/LC_MESSAGES/django.po new file mode 100644 index 00000000..d5460c47 --- /dev/null +++ b/cmsplugin_filer_video/locale/nb/LC_MESSAGES/django.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Norwegian Bokmål (http://www.transifex.com/divio/cmsplugin-filer/language/nb/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nb\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "" + +#: models.py:17 +msgid "movie file" +msgstr "" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "" + +#: models.py:23 +msgid "movie url" +msgstr "" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "" + +#: models.py:25 +msgid "image" +msgstr "" + +#: models.py:26 +msgid "preview image file" +msgstr "" + +#: models.py:33 +msgid "width" +msgstr "" + +#: models.py:34 +msgid "height" +msgstr "" + +#: models.py:36 +msgid "auto play" +msgstr "" + +#: models.py:37 +msgid "auto hide" +msgstr "" + +#: models.py:38 +msgid "fullscreen" +msgstr "" + +#: models.py:39 +msgid "loop" +msgstr "" + +#: models.py:42 +msgid "background color" +msgstr "" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "" + +#: models.py:43 +msgid "text color" +msgstr "" + +#: models.py:44 +msgid "seekbar color" +msgstr "" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "" + +#: models.py:46 +msgid "loadingbar color" +msgstr "" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "" diff --git a/cmsplugin_filer_video/locale/nl/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/nl/LC_MESSAGES/django.po new file mode 100644 index 00000000..12293968 --- /dev/null +++ b/cmsplugin_filer_video/locale/nl/LC_MESSAGES/django.po @@ -0,0 +1,121 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Maarten Draijer , 2013 +# Maikel Wever, 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Dutch (http://www.transifex.com/divio/cmsplugin-filer/language/nl/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Video" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Kleurinstellingen" + +#: models.py:17 +msgid "movie file" +msgstr "filmbestand" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "gebruik een .flv of h264 gecodeerd videobestand" + +#: models.py:23 +msgid "movie url" +msgstr "film url" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "vimeo of youtube video url. Bijvoorbeeld: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "afbeelding" + +#: models.py:26 +msgid "preview image file" +msgstr "bekijk voorbeeld van afbeeldingsbestand" + +#: models.py:33 +msgid "width" +msgstr "breedte" + +#: models.py:34 +msgid "height" +msgstr "hoogte" + +#: models.py:36 +msgid "auto play" +msgstr "automatisch afspelen" + +#: models.py:37 +msgid "auto hide" +msgstr "automatisch verbergen" + +#: models.py:38 +msgid "fullscreen" +msgstr "volledig scherm" + +#: models.py:39 +msgid "loop" +msgstr "lus" + +#: models.py:42 +msgid "background color" +msgstr "achtergrondkleur" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Hexadecimaal, bijv. ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "tekstkleur" + +#: models.py:44 +msgid "seekbar color" +msgstr "zoekbalk kleur" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "zoekbalk achtergrondkleur" + +#: models.py:46 +msgid "loadingbar color" +msgstr "laadbalk kleur" + +#: models.py:47 +msgid "button out color" +msgstr "uitgaande button kleur" + +#: models.py:48 +msgid "button over color" +msgstr "button over kleur" + +#: models.py:49 +msgid "button highlight color" +msgstr "button markeer kleur" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Flash plugin ontbreekt. Download hier." diff --git a/cmsplugin_filer_video/locale/nn/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/nn/LC_MESSAGES/django.po new file mode 100644 index 00000000..201099fb --- /dev/null +++ b/cmsplugin_filer_video/locale/nn/LC_MESSAGES/django.po @@ -0,0 +1,119 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Norwegian Nynorsk (http://www.transifex.com/divio/cmsplugin-filer/language/nn/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: nn\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "" + +#: models.py:17 +msgid "movie file" +msgstr "" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "" + +#: models.py:23 +msgid "movie url" +msgstr "" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "" + +#: models.py:25 +msgid "image" +msgstr "" + +#: models.py:26 +msgid "preview image file" +msgstr "" + +#: models.py:33 +msgid "width" +msgstr "" + +#: models.py:34 +msgid "height" +msgstr "" + +#: models.py:36 +msgid "auto play" +msgstr "" + +#: models.py:37 +msgid "auto hide" +msgstr "" + +#: models.py:38 +msgid "fullscreen" +msgstr "" + +#: models.py:39 +msgid "loop" +msgstr "" + +#: models.py:42 +msgid "background color" +msgstr "" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "" + +#: models.py:43 +msgid "text color" +msgstr "" + +#: models.py:44 +msgid "seekbar color" +msgstr "" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "" + +#: models.py:46 +msgid "loadingbar color" +msgstr "" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "" diff --git a/cmsplugin_filer_video/locale/pl_PL/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/pl_PL/LC_MESSAGES/django.po new file mode 100644 index 00000000..8dada183 --- /dev/null +++ b/cmsplugin_filer_video/locale/pl_PL/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Mateusz Marzantowicz , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/divio/cmsplugin-filer/language/pl_PL/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Wideo" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Ustawienia Kolorów" + +#: models.py:17 +msgid "movie file" +msgstr "plik filmu" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "użyj pliku flv lub h264" + +#: models.py:23 +msgid "movie url" +msgstr "url filmu" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "Adres url w serwisie Vimeo lub Youtube. Np.: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "obraz" + +#: models.py:26 +msgid "preview image file" +msgstr "podgląd obrazu" + +#: models.py:33 +msgid "width" +msgstr "szerokość" + +#: models.py:34 +msgid "height" +msgstr "wysokość" + +#: models.py:36 +msgid "auto play" +msgstr "automatyczne odtwarzanie" + +#: models.py:37 +msgid "auto hide" +msgstr "automatyczne ukrywanie" + +#: models.py:38 +msgid "fullscreen" +msgstr "pełny ekran" + +#: models.py:39 +msgid "loop" +msgstr "" + +#: models.py:42 +msgid "background color" +msgstr "kolor tła" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Szesnastkowo, np. ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "kolor tekstu" + +#: models.py:44 +msgid "seekbar color" +msgstr "" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "" + +#: models.py:46 +msgid "loadingbar color" +msgstr "" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Brak wtyczki flash. Pobierz ją tutaj." diff --git a/cmsplugin_filer_video/locale/pt_BR/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/pt_BR/LC_MESSAGES/django.po new file mode 100644 index 00000000..2eb68835 --- /dev/null +++ b/cmsplugin_filer_video/locale/pt_BR/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Rodrigo , 2013 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Portuguese (Brazil) (http://www.transifex.com/divio/cmsplugin-filer/language/pt_BR/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Vídeo" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Ajustes de cor" + +#: models.py:17 +msgid "movie file" +msgstr "arquivo de vídeo" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "utilizar arquivos de vídeo encodados como .flv ou h264" + +#: models.py:23 +msgid "movie url" +msgstr "url do vídeo" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "url de vídeo do vimeo ou do youtube. Exemplo: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "imagem" + +#: models.py:26 +msgid "preview image file" +msgstr "arquivo de preview de imagem" + +#: models.py:33 +msgid "width" +msgstr "largura" + +#: models.py:34 +msgid "height" +msgstr "altura" + +#: models.py:36 +msgid "auto play" +msgstr "auto play" + +#: models.py:37 +msgid "auto hide" +msgstr "auto hide" + +#: models.py:38 +msgid "fullscreen" +msgstr "tela cheia" + +#: models.py:39 +msgid "loop" +msgstr "loop" + +#: models.py:42 +msgid "background color" +msgstr "cor de fundo" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Hexadecimal, ex.: ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "cor do texto" + +#: models.py:44 +msgid "seekbar color" +msgstr "Cor da barra de progresso" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "Cor de fundo da barra de progresso" + +#: models.py:46 +msgid "loadingbar color" +msgstr "Cor da barra de carregamento" + +#: models.py:47 +msgid "button out color" +msgstr "Cor externa do botão" + +#: models.py:48 +msgid "button over color" +msgstr "Cor de over do botão" + +#: models.py:49 +msgid "button highlight color" +msgstr "Cor de seleção do botão" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Plugin do Flash não encontrado. Baixe aqui." diff --git a/cmsplugin_filer_video/locale/ru/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 00000000..0737efd3 --- /dev/null +++ b/cmsplugin_filer_video/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Pavel , 2012 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Russian (http://www.transifex.com/divio/cmsplugin-filer/language/ru/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "Видео" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "Настройки цветов" + +#: models.py:17 +msgid "movie file" +msgstr "видеофайл" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "используйте файл с расширением .flv или видео, кодированное h264" + +#: models.py:23 +msgid "movie url" +msgstr "ссылка на видео" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "ссылка на видео на youtube или vimeo" + +#: models.py:25 +msgid "image" +msgstr "изображение" + +#: models.py:26 +msgid "preview image file" +msgstr "изображение для превью" + +#: models.py:33 +msgid "width" +msgstr "ширина" + +#: models.py:34 +msgid "height" +msgstr "высота" + +#: models.py:36 +msgid "auto play" +msgstr "автозапуск" + +#: models.py:37 +msgid "auto hide" +msgstr "автоскрытие" + +#: models.py:38 +msgid "fullscreen" +msgstr "полный экран" + +#: models.py:39 +msgid "loop" +msgstr "цикл" + +#: models.py:42 +msgid "background color" +msgstr "цвет фона" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "Шестнадцатиричное число, например ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "цвет текста" + +#: models.py:44 +msgid "seekbar color" +msgstr "цвет ползунка" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "цвет фона ползунка" + +#: models.py:46 +msgid "loadingbar color" +msgstr "цвет блока загрузки" + +#: models.py:47 +msgid "button out color" +msgstr "цвет кнопки без наведения" + +#: models.py:48 +msgid "button over color" +msgstr "цвет кнопки при наведении" + +#: models.py:49 +msgid "button highlight color" +msgstr "цвет подсвеченной кнопки" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "Недоступен флеш-плагин. Скачайте здесь." diff --git a/cmsplugin_filer_video/locale/zh_CN/LC_MESSAGES/django.po b/cmsplugin_filer_video/locale/zh_CN/LC_MESSAGES/django.po new file mode 100644 index 00000000..8b896624 --- /dev/null +++ b/cmsplugin_filer_video/locale/zh_CN/LC_MESSAGES/django.po @@ -0,0 +1,120 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Calvin-He , 2015 +msgid "" +msgstr "" +"Project-Id-Version: django Filer Plugins\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-08-23 10:26-0400\n" +"PO-Revision-Date: 2016-04-26 12:16+0000\n" +"Last-Translator: Stefan Foulis \n" +"Language-Team: Chinese (China) (http://www.transifex.com/divio/cmsplugin-filer/language/zh_CN/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: cms_plugins.py:15 +msgid "Video" +msgstr "视频" + +#: cms_plugins.py:49 +msgid "Color Settings" +msgstr "颜色设置" + +#: models.py:17 +msgid "movie file" +msgstr "影视文件" + +#: models.py:18 +msgid "use .flv file or h264 encoded video file" +msgstr "使用.flv或h264编码的视频文件" + +#: models.py:23 +msgid "movie url" +msgstr "影视url" + +#: models.py:23 +msgid "" +"vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-" +"kpo" +msgstr "vimeo或youtube上的视频url, 比如: http://www.youtube.com/watch?v=YFa59lK-kpo" + +#: models.py:25 +msgid "image" +msgstr "图片" + +#: models.py:26 +msgid "preview image file" +msgstr "预览图片文件" + +#: models.py:33 +msgid "width" +msgstr "宽" + +#: models.py:34 +msgid "height" +msgstr "高" + +#: models.py:36 +msgid "auto play" +msgstr "自动播放" + +#: models.py:37 +msgid "auto hide" +msgstr "自动隐藏" + +#: models.py:38 +msgid "fullscreen" +msgstr "全屏" + +#: models.py:39 +msgid "loop" +msgstr "循环播放" + +#: models.py:42 +msgid "background color" +msgstr "背景颜色" + +#: models.py:42 models.py:43 models.py:44 models.py:45 models.py:46 +#: models.py:47 models.py:48 models.py:49 +msgid "Hexadecimal, eg ff00cc" +msgstr "十六进制颜色值,如ff00cc" + +#: models.py:43 +msgid "text color" +msgstr "文本颜色" + +#: models.py:44 +msgid "seekbar color" +msgstr "拖动条颜色" + +#: models.py:45 +msgid "seekbar bg color" +msgstr "拖动条背景颜色" + +#: models.py:46 +msgid "loadingbar color" +msgstr "加载条颜色" + +#: models.py:47 +msgid "button out color" +msgstr "" + +#: models.py:48 +msgid "button over color" +msgstr "" + +#: models.py:49 +msgid "button highlight color" +msgstr "按钮高亮颜色" + +#: templates/cmsplugin_filer_video/video.html:3 +msgid "" +"Missing flash plugin. Download here." +msgstr "缺失flash插件。请 下载." diff --git a/cmsplugin_filer_video/migrations/0001_initial.py b/cmsplugin_filer_video/migrations/0001_initial.py new file mode 100644 index 00000000..7c57c55e --- /dev/null +++ b/cmsplugin_filer_video/migrations/0001_initial.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import filer.fields.image +import filer.fields.file +from cmsplugin_filer_video import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('cms', '0003_auto_20140926_2347'), + ('filer', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='FilerVideo', + fields=[ + ('cmsplugin_ptr', models.OneToOneField(parent_link=True, auto_created=True, primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE)), + ('movie_url', models.CharField(help_text='vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-kpo', max_length=255, null=True, verbose_name='movie url', blank=True)), + ('width', models.PositiveSmallIntegerField(verbose_name='width', default=settings.VIDEO_WIDTH)), + ('height', models.PositiveSmallIntegerField(verbose_name='height', default=settings.VIDEO_HEIGHT)), + ('auto_play', models.BooleanField(verbose_name='auto play', default=settings.VIDEO_AUTOPLAY)), + ('auto_hide', models.BooleanField(verbose_name='auto hide', default=settings.VIDEO_AUTOHIDE)), + ('fullscreen', models.BooleanField(verbose_name='fullscreen', default=settings.VIDEO_FULLSCREEN)), + ('loop', models.BooleanField(verbose_name='loop', default=settings.VIDEO_LOOP)), + ('bgcolor', models.CharField(default=settings.VIDEO_BG_COLOR, max_length=6, verbose_name='background color', help_text='Hexadecimal, eg ff00cc')), + ('textcolor', models.CharField(default=settings.VIDEO_TEXT_COLOR, max_length=6, verbose_name='text color', help_text='Hexadecimal, eg ff00cc')), + ('seekbarcolor', models.CharField(default=settings.VIDEO_SEEKBAR_COLOR, max_length=6, verbose_name='seekbar color', help_text='Hexadecimal, eg ff00cc')), + ('seekbarbgcolor', models.CharField(default=settings.VIDEO_SEEKBARBG_COLOR, max_length=6, verbose_name='seekbar bg color', help_text='Hexadecimal, eg ff00cc')), + ('loadingbarcolor', models.CharField(default=settings.VIDEO_LOADINGBAR_COLOR, max_length=6, verbose_name='loadingbar color', help_text='Hexadecimal, eg ff00cc')), + ('buttonoutcolor', models.CharField(default=settings.VIDEO_BUTTON_OUT_COLOR, max_length=6, verbose_name='button out color', help_text='Hexadecimal, eg ff00cc')), + ('buttonovercolor', models.CharField(default=settings.VIDEO_BUTTON_OVER_COLOR, max_length=6, verbose_name='button over color', help_text='Hexadecimal, eg ff00cc')), + ('buttonhighlightcolor', models.CharField(default=settings.VIDEO_BUTTON_HIGHLIGHT_COLOR, max_length=6, verbose_name='button highlight color', help_text='Hexadecimal, eg ff00cc')), + ('image', filer.fields.image.FilerImageField(null=True, to='filer.Image', help_text='preview image file', related_name='filer_video_image', blank=True, verbose_name='image', on_delete=models.CASCADE)), + ('movie', filer.fields.file.FilerFileField(null=True, to='filer.File', help_text='use .flv file or h264 encoded video file', blank=True, verbose_name='movie file', on_delete=models.CASCADE)), + ], + options={ + 'abstract': False, + }, + bases=('cms.cmsplugin',), + ), + ] diff --git a/cmsplugin_filer_video/migrations/0002_auto_20160108_1710.py b/cmsplugin_filer_video/migrations/0002_auto_20160108_1710.py new file mode 100644 index 00000000..9f14c068 --- /dev/null +++ b/cmsplugin_filer_video/migrations/0002_auto_20160108_1710.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import filer.fields.image +import django.db.models.deletion +import filer.fields.file + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_video', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='filervideo', + name='image', + field=filer.fields.image.FilerImageField(related_name='filer_video_image', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='filer.Image', help_text='preview image file', null=True, verbose_name='image'), + ), + migrations.AlterField( + model_name='filervideo', + name='movie', + field=filer.fields.file.FilerFileField(on_delete=django.db.models.deletion.SET_NULL, blank=True, to='filer.File', help_text='use .flv file or h264 encoded video file', null=True, verbose_name='movie file'), + ), + ] diff --git a/cmsplugin_filer_video/migrations/0003_auto_20160713_1853.py b/cmsplugin_filer_video/migrations/0003_auto_20160713_1853.py new file mode 100644 index 00000000..fdccfc4e --- /dev/null +++ b/cmsplugin_filer_video/migrations/0003_auto_20160713_1853.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cmsplugin_filer_video', '0002_auto_20160108_1710'), + ] + + operations = [ + migrations.AlterField( + model_name='filervideo', + name='cmsplugin_ptr', + field=models.OneToOneField(parent_link=True, related_name='cmsplugin_filer_video_filervideo', primary_key=True, serialize=False, to='cms.CMSPlugin', on_delete=models.CASCADE), + ), + ] diff --git a/cmsplugin_filer_video/migrations/__init__.py b/cmsplugin_filer_video/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cmsplugin_filer_video/models.py b/cmsplugin_filer_video/models.py new file mode 100644 index 00000000..009c84f9 --- /dev/null +++ b/cmsplugin_filer_video/models.py @@ -0,0 +1,72 @@ +from __future__ import unicode_literals + +from cms.models import CMSPlugin +from cmsplugin_filer_video import settings +from django.db import models +from django.utils.translation import ugettext_lazy as _ +from filer.fields.file import FilerFileField +from filer.fields.image import FilerImageField +from os.path import basename + + +class FilerVideo(CMSPlugin): + # player settings + movie = FilerFileField( + verbose_name=_('movie file'), + help_text=_('use .flv file or h264 encoded video file'), + blank=True, + null=True, + on_delete=models.SET_NULL, + ) + movie_url = models.CharField(_('movie url'), max_length=255, help_text=_('vimeo or youtube video url. Example: http://www.youtube.com/watch?v=YFa59lK-kpo'), blank=True, null=True) + image = FilerImageField( + verbose_name=_('image'), + help_text=_('preview image file'), + null=True, + blank=True, + related_name='filer_video_image', + on_delete=models.SET_NULL, + ) + + width = models.PositiveSmallIntegerField(_('width'), default=settings.VIDEO_WIDTH) + height = models.PositiveSmallIntegerField(_('height'), default=settings.VIDEO_HEIGHT) + + auto_play = models.BooleanField(_('auto play'), default=settings.VIDEO_AUTOPLAY) + auto_hide = models.BooleanField(_('auto hide'), default=settings.VIDEO_AUTOHIDE) + fullscreen = models.BooleanField(_('fullscreen'), default=settings.VIDEO_FULLSCREEN) + loop = models.BooleanField(_('loop'), default=settings.VIDEO_LOOP) + + # plugin settings + bgcolor = models.CharField(_('background color'), max_length=6, default=settings.VIDEO_BG_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + textcolor = models.CharField(_('text color'), max_length=6, default=settings.VIDEO_TEXT_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + seekbarcolor = models.CharField(_('seekbar color'), max_length=6, default=settings.VIDEO_SEEKBAR_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + seekbarbgcolor = models.CharField(_('seekbar bg color'), max_length=6, default=settings.VIDEO_SEEKBARBG_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + loadingbarcolor = models.CharField(_('loadingbar color'), max_length=6, default=settings.VIDEO_LOADINGBAR_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + buttonoutcolor = models.CharField(_('button out color'), max_length=6, default=settings.VIDEO_BUTTON_OUT_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + buttonovercolor = models.CharField(_('button over color'), max_length=6, default=settings.VIDEO_BUTTON_OVER_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + buttonhighlightcolor = models.CharField(_('button highlight color'), max_length=6, default=settings.VIDEO_BUTTON_HIGHLIGHT_COLOR, help_text=_('Hexadecimal, eg ff00cc')) + cmsplugin_ptr = models.OneToOneField( + to=CMSPlugin, + related_name='%(app_label)s_%(class)s', + parent_link=True, + on_delete=models.CASCADE + ) + + def __str__(self): + if self.movie: + name = self.movie.path + else: + name = self.movie_url + return "%s" % basename(name) + + def get_height(self): + return "%s" % (self.height) + + def get_width(self): + return "%s" % (self.width) + + def get_movie(self): + if self.movie: + return self.movie.url + else: + return self.movie_url diff --git a/cmsplugin_filer_video/settings.py b/cmsplugin_filer_video/settings.py new file mode 100644 index 00000000..4d61eeec --- /dev/null +++ b/cmsplugin_filer_video/settings.py @@ -0,0 +1,20 @@ +from django.conf import settings + +VIDEO_WIDTH = getattr(settings, "VIDEO_WIDTH", 320) +VIDEO_HEIGHT = getattr(settings, "VIDEO_HEIGHT", 240) + +VIDEO_AUTOPLAY = getattr(settings, "VIDEO_AUTOPLAY", False) +VIDEO_AUTOHIDE = getattr(settings, "VIDEO_AUTOHIDE", False) +VIDEO_FULLSCREEN = getattr(settings, "VIDEO_FULLSCREEN", True) +VIDEO_LOOP = getattr(settings, "VIDEO_LOOP", False) + +VIDEO_BG_COLOR = getattr(settings, "VIDEO_BG_COLOR", "000000") +VIDEO_TEXT_COLOR = getattr(settings, "VIDEO_TEXT_COLOR", "FFFFFF") +VIDEO_SEEKBAR_COLOR = getattr(settings, "VIDEO_SEEKBAR_COLOR", "13ABEC") +VIDEO_SEEKBARBG_COLOR = getattr(settings, "VIDEO_SEEKBARBG_COLOR", "333333") +VIDEO_LOADINGBAR_COLOR = getattr(settings, "VIDEO_LOADINGBAR_COLOR", "828282") +VIDEO_BUTTON_OUT_COLOR = getattr(settings, "VIDEO_BUTTON_OUT_COLOR", "333333") +VIDEO_BUTTON_OVER_COLOR = getattr(settings, "VIDEO_BUTTON_OVER_COLOR", "000000") +VIDEO_BUTTON_HIGHLIGHT_COLOR = getattr(settings, "VIDEO_BUTTON_HIGHLIGHT_COLOR", "FFFFFF") + +VIDEO_PLUGIN_ENABLE_ADVANCED_SETTINGS = getattr(settings, "VIDEO_PLUGIN_ENABLE_ADVANCED_SETTINGS", True) diff --git a/cmsplugin_filer_video/templates/cmsplugin_filer_video/video.html b/cmsplugin_filer_video/templates/cmsplugin_filer_video/video.html new file mode 100644 index 00000000..92c957b9 --- /dev/null +++ b/cmsplugin_filer_video/templates/cmsplugin_filer_video/video.html @@ -0,0 +1,46 @@ +{% load i18n sekizai_tags cmsplugin_filer_js_tmp %} + +
    {% trans 'Missing flash plugin. Download here.' %}
    + +{% addtoblock "js" %} + +{% endaddtoblock %} + +{% addtoblock "js" %} + +{% endaddtoblock %} diff --git a/cmsplugin_filer_video/templatetags/__init__.py b/cmsplugin_filer_video/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/cmsplugin_filer_video/templatetags/cmsplugin_filer_js_tmp.py b/cmsplugin_filer_video/templatetags/cmsplugin_filer_js_tmp.py new file mode 100644 index 00000000..19b5b982 --- /dev/null +++ b/cmsplugin_filer_video/templatetags/cmsplugin_filer_js_tmp.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from django.template import Library +# TODO: Remove this templatetag entirely when the minimum supported Django-CMS version will be moved to 2.3 and updated the template to use cms_js_tags templatetag + + +register = Library() + + +@register.filter +def bool(value): + if value: + return 'true' + else: + return 'false' diff --git a/stored_messages/LICENSE b/stored_messages/LICENSE new file mode 100644 index 00000000..1cea11e2 --- /dev/null +++ b/stored_messages/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2013, evonove +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 django-stored-messages nor the names of its 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 HOLDER 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. \ No newline at end of file diff --git a/stored_messages/__init__.py b/stored_messages/__init__.py new file mode 100644 index 00000000..9080d090 --- /dev/null +++ b/stored_messages/__init__.py @@ -0,0 +1,11 @@ +# flake8: noqa +from __future__ import absolute_import + +from django.contrib.messages.api import * +from stored_messages.api import * +from stored_messages.constants import * + + +__version__ = '1.4.0' + +VERSION = __version__ # synonym diff --git a/stored_messages/admin.py b/stored_messages/admin.py new file mode 100644 index 00000000..8447ee02 --- /dev/null +++ b/stored_messages/admin.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from .models import Inbox, Message, MessageArchive + + +admin.site.register(Inbox) +admin.site.register(Message) +admin.site.register(MessageArchive) diff --git a/stored_messages/api.py b/stored_messages/api.py new file mode 100644 index 00000000..697be93e --- /dev/null +++ b/stored_messages/api.py @@ -0,0 +1,69 @@ +from .settings import stored_messages_settings + + +__all__ = ( + 'add_message_for', + 'broadcast_message', + 'mark_read', + 'mark_all_read', +) + + +def add_message_for(users, level, message_text, extra_tags='', date=None, url=None, fail_silently=False): + """ + Send a message to a list of users without passing through `django.contrib.messages` + + :param users: an iterable containing the recipients of the messages + :param level: message level + :param message_text: the string containing the message + :param extra_tags: like the Django api, a string containing extra tags for the message + :param date: a date, different than the default timezone.now + :param url: an optional url + :param fail_silently: not used at the moment + """ + BackendClass = stored_messages_settings.STORAGE_BACKEND + backend = BackendClass() + m = backend.create_message(level, message_text, extra_tags, date, url) + backend.archive_store(users, m) + backend.inbox_store(users, m) + + +def broadcast_message(level, message_text, extra_tags='', date=None, url=None, fail_silently=False): + """ + Send a message to all users aka broadcast. + + :param level: message level + :param message_text: the string containing the message + :param extra_tags: like the Django api, a string containing extra tags for the message + :param date: a date, different than the default timezone.now + :param url: an optional url + :param fail_silently: not used at the moment + """ + from django.contrib.auth import get_user_model + users = get_user_model().objects.all() + add_message_for(users, level, message_text, extra_tags=extra_tags, date=date, url=url, fail_silently=fail_silently) + + +def mark_read(user, message): + """ + Mark message instance as read for user. + Returns True if the message was `unread` and thus actually marked as `read` or False in case + it is already `read` or it does not exist at all. + + :param user: user instance for the recipient + :param message: a Message instance to mark as read + """ + BackendClass = stored_messages_settings.STORAGE_BACKEND + backend = BackendClass() + backend.inbox_delete(user, message) + + +def mark_all_read(user): + """ + Mark all message instances for a user as read. + + :param user: user instance for the recipient + """ + BackendClass = stored_messages_settings.STORAGE_BACKEND + backend = BackendClass() + backend.inbox_purge(user) diff --git a/stored_messages/backends/__init__.py b/stored_messages/backends/__init__.py new file mode 100644 index 00000000..78ce3276 --- /dev/null +++ b/stored_messages/backends/__init__.py @@ -0,0 +1,3 @@ +# flake8: noqa +from .default.backend import DefaultBackend +from .redis.backend import RedisBackend diff --git a/stored_messages/backends/base.py b/stored_messages/backends/base.py new file mode 100644 index 00000000..df8a026b --- /dev/null +++ b/stored_messages/backends/base.py @@ -0,0 +1,159 @@ + +class StoredMessagesBackend(object): + """ + + """ + def create_message(self, level, msg_text, extra_tags, date=None): + """ + Create and return a `Message` instance. + Instance types depend on backends implementation. + + Params: + `level`: message level (see django.contrib.messages) + `msg_text`: what you think it is + `extra_tags`: see django.contrib.messages + `date`: a DateTime (optional) + + Return: + `Message` instance + """ + raise NotImplementedError() + + def inbox_list(self, user): + """ + Retrieve all the messages in `user`'s Inbox. + + Params: + `user`: Django User instance + + Return: + An iterable containing `Message` instances + """ + raise NotImplementedError() + + def inbox_purge(self, user): + """ + Delete all the messages in `user`'s Inbox. + + Params: + user: Django User instance + + Return: + None + """ + raise NotImplementedError() + + def inbox_store(self, users, msg_instance): + """ + Store a `Message` instance in the inbox for a list + of users. + + Params: + users: a list or iterable containing Django User instances + msg_instance: Message instance to persist in inbox + + Return: + None + + Raise: + MessageTypeNotSupported if `msg_instance` cannot be managed by current backend + """ + raise NotImplementedError() + + def inbox_delete(self, user, msg_id): + """ + Remove a `Message` instance from `user`'s inbox. + + Params: + user: Django User instance + msg_id: Message identifier + + Return: + None + + Raise: + MessageDoesNotExist if msg_id was not found + """ + raise NotImplementedError() + + def inbox_get(self, user, msg_id): + """ + Retrieve a `Message` instance from `user`'s inbox. + + Params: + user: Django User instance + msg_id: Message identifier + + Return: + A `Message` instance + + Raise: + MessageDoesNotExist if msg_id was not found + """ + + def archive_store(self, users, msg_instance): + """ + Store a `Message` instance in the archive for a list + of users. + + Params: + users: a list or iterable containing Django User instances + msg_instance: Message instance to persist in archive + + Return: + None + + Raise: + MessageTypeNotSupported if `msg_instance` cannot be managed by current backend + """ + raise NotImplementedError() + + def archive_list(self, user): + """ + Retrieve all the messages in `user`'s archive. + + Params: + user: Django User instance + + Return: + An iterable containing `Message` instances + """ + raise NotImplementedError() + + def can_handle(self, msg_instance): + """ + Determine if this backend can handle messages + of the same type of `msg_instance`. + + Params: + `msg_instance`: `Message` instance + + Return: + True if type is correct, False otherwise + """ + raise NotImplementedError() + + def expired_messages_cleanup(self): + """ + Remove messages that have been expired. + + Params: + None + + Return: + None + """ + raise NotImplementedError() + + def _flush(self): + """ + Clear all backend data. + Warning: heavily destructive! Here for convenience, not used by the API anyway. + + Params: + None + + Return: + None + """ + raise NotImplementedError() diff --git a/stored_messages/backends/default/__init__.py b/stored_messages/backends/default/__init__.py new file mode 100644 index 00000000..e4a0cd42 --- /dev/null +++ b/stored_messages/backends/default/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa +from .backend import DefaultBackend diff --git a/stored_messages/backends/default/backend.py b/stored_messages/backends/default/backend.py new file mode 100644 index 00000000..822b502a --- /dev/null +++ b/stored_messages/backends/default/backend.py @@ -0,0 +1,81 @@ +from django.utils import timezone + +from ..base import StoredMessagesBackend +from ..exceptions import MessageTypeNotSupported, MessageDoesNotExist +from .. import signals +from ...models import Inbox, Message, MessageArchive +from ...settings import stored_messages_settings + + +class DefaultBackend(StoredMessagesBackend): + """ + + """ + def inbox_list(self, user): + if user.is_anonymous(): + return [] + inbox = Inbox.objects.filter(user=user).select_related("message") + return [m.message for m in inbox] + + def inbox_purge(self, user): + if user.is_authenticated(): + Inbox.objects.filter(user=user).delete() + signals.inbox_purged.send(sender=self.__class__, user=user) + + def inbox_store(self, users, msg_instance): + if not self.can_handle(msg_instance): + raise MessageTypeNotSupported() + + for user in users: + Inbox.objects.get_or_create(user=user, message=msg_instance) + signals.inbox_stored.send(sender=self.__class__, user=user, message=msg_instance) + + def inbox_delete(self, user, msg_id): + try: + inbox_m = Inbox.objects.filter(user=user, message=msg_id).get() + inbox_m.delete() + signals.inbox_deleted.send(sender=self.__class__, user=user, message_id=msg_id) + except Inbox.DoesNotExist: + raise MessageDoesNotExist("Message with id %s does not exist" % msg_id) + + def inbox_get(self, user, msg_id): + try: + return Inbox.objects.get(pk=msg_id).message + except Inbox.DoesNotExist: + raise MessageDoesNotExist("Message with id %s does not exist" % msg_id) + + def create_message(self, level, msg_text, extra_tags='', date=None, url=None): + kwargs = { + 'message': msg_text, + 'level': level, + 'tags': extra_tags, + 'url': url, + } + if date: + kwargs['date'] = date + m_instance = Message.objects.create(**kwargs) + return m_instance + + def archive_store(self, users, msg_instance): + if not self.can_handle(msg_instance): + raise MessageTypeNotSupported() + + for user in users: + MessageArchive.objects.create(user=user, message=msg_instance) + signals.archive_stored.send(sender=self.__class__, user=user, message=msg_instance) + + def archive_list(self, user): + return list(MessageArchive.objects.filter(user=user)) + + def can_handle(self, message): + return isinstance(message, Message) + + def expired_messages_cleanup(self): + expiration_date = timezone.now() + timezone.timedelta( + days=-stored_messages_settings.MESSAGE_EXPIRE_DAYS) + Message.objects.filter(date__lte=expiration_date).delete() + + def _flush(self): + Inbox.objects.all().delete() + MessageArchive.objects.all().delete() + Message.objects.all().delete() diff --git a/stored_messages/backends/exceptions.py b/stored_messages/backends/exceptions.py new file mode 100644 index 00000000..12046f4d --- /dev/null +++ b/stored_messages/backends/exceptions.py @@ -0,0 +1,8 @@ + + +class MessageTypeNotSupported(Exception): + pass + + +class MessageDoesNotExist(Exception): + pass diff --git a/stored_messages/backends/redis/__init__.py b/stored_messages/backends/redis/__init__.py new file mode 100644 index 00000000..cdfb2bbf --- /dev/null +++ b/stored_messages/backends/redis/__init__.py @@ -0,0 +1,2 @@ +# flake8: noqa +from .backend import RedisBackend, Message diff --git a/stored_messages/backends/redis/backend.py b/stored_messages/backends/redis/backend.py new file mode 100644 index 00000000..61c5fcf5 --- /dev/null +++ b/stored_messages/backends/redis/backend.py @@ -0,0 +1,143 @@ +from __future__ import unicode_literals + +from django.utils import timezone +from django.utils.encoding import force_text +from django.core.serializers.json import DjangoJSONEncoder + +import json +from collections import namedtuple +import hashlib + +from ..exceptions import MessageTypeNotSupported, MessageDoesNotExist +from ..base import StoredMessagesBackend +from .. import signals +from ...settings import stored_messages_settings + +try: + # Let django project bootstrap anyway when not using this backend + import redis +except ImportError: + pass + + +Message = namedtuple('Message', ['id', 'message', 'level', 'tags', 'date', 'url']) + + +class RedisBackend(StoredMessagesBackend): + """ + + """ + def __init__(self): + self.client = redis.StrictRedis.from_url(stored_messages_settings.REDIS_URL) + + def _flush(self): + self.client.flushdb() + + def _toJSON(self, msg_instance): + """ + Dump a Message instance into a JSON string + """ + return json.dumps(msg_instance._asdict(), cls=DjangoJSONEncoder) + + def _fromJSON(self, json_msg): + """ + Return a Message instance built from data contained in a JSON string + """ + return Message(**json.loads(force_text(json_msg))) + + def _list_key(self, key): + """ + boilerplate + """ + ret = [] + for msg_json in self.client.lrange(key, 0, -1): + ret.append(self._fromJSON(msg_json)) + return ret + + def _list(self, key_tpl, user): + return self._list_key(key_tpl % user.pk) + + def create_message(self, level, msg_text, extra_tags='', date=None, url=None): + """ + Message instances are namedtuples of type `Message`. + The date field is already serialized in datetime.isoformat ECMA-262 format + """ + if not date: + now = timezone.now() + else: + now = date + r = now.isoformat() + if now.microsecond: + r = r[:23] + r[26:] + if r.endswith('+00:00'): + r = r[:-6] + 'Z' + + fingerprint = r + msg_text + + msg_id = hashlib.sha256(fingerprint.encode('ascii', 'ignore')).hexdigest() + return Message(id=msg_id, message=msg_text, level=level, tags=extra_tags, date=r, url=url) + + def inbox_list(self, user): + if user.is_anonymous(): + return [] + return self._list('user:%d:notifications', user) + + def inbox_purge(self, user): + if user.is_authenticated(): + self.client.delete('user:%d:notifications' % user.pk) + self.client.delete('user:%d:notificationsidx' % user.pk) + signals.inbox_purged.send(sender=self.__class__, user=user) + + def inbox_store(self, users, msg_instance): + if not self.can_handle(msg_instance): + raise MessageTypeNotSupported() + + for user in users: + if self.client.sismember('user:%d:notificationsidx' % user.pk, msg_instance.id): + return # a duplicate, NOOP + else: + self.client.sadd('user:%d:notificationsidx' % user.pk, msg_instance.id) + + self.client.rpush('user:%d:notifications' % user.pk, self._toJSON(msg_instance)) + signals.inbox_stored.send(sender=self.__class__, user=user, message=msg_instance) + + def inbox_delete(self, user, msg_id): + for m in self._list('user:%d:notifications', user): + if m.id == msg_id: + msg = self.client.lrem('user:%d:notifications' % user.pk, 0, json.dumps(m._asdict())) + signals.inbox_deleted.send(sender=self.__class__, user=user, message_id=msg_id) + return msg + raise MessageDoesNotExist("Message with id %s does not exist" % msg_id) + + def inbox_get(self, user, msg_id): + for m in self._list('user:%d:notifications', user): + if m.id == msg_id: + return m + raise MessageDoesNotExist("Message with id %s does not exist" % msg_id) + + def archive_store(self, users, msg_instance): + if not self.can_handle(msg_instance): + raise MessageTypeNotSupported() + + for user in users: + self.client.rpush('user:%d:archive' % user.pk, self._toJSON(msg_instance)) + signals.archive_stored.send(sender=self.__class__, user=user, message=msg_instance) + + def archive_list(self, user): + return self._list('user:%d:archive', user) + + def can_handle(self, msg_instance): + return isinstance(msg_instance, Message) + + def expired_messages_cleanup(self): + expiration_date = timezone.now() + timezone.timedelta( + days=-stored_messages_settings.MESSAGE_EXPIRE_DAYS) + keys = self.client.keys('user:*:notifications') + for k in keys: + _, user, _ = k.split(':') + for m in self._list_key(k): + if m.date <= expiration_date: + msg = json.dumps(m._asdict()) + self.client.lrem(k, 0, msg) + self.client.srem('user:%s:notificationsidx' % user, m.id) + self.client.rpop('user:%d:archive' % user, msg) diff --git a/stored_messages/backends/signals.py b/stored_messages/backends/signals.py new file mode 100644 index 00000000..8d394260 --- /dev/null +++ b/stored_messages/backends/signals.py @@ -0,0 +1,7 @@ +from django.dispatch import Signal + +inbox_stored = Signal(providing_args=["user", "message"]) +inbox_deleted = Signal(providing_args=["user", "message_id"]) +inbox_purged = Signal(providing_args=["user"]) + +archive_stored = Signal(providing_args=["user", "message"]) diff --git a/stored_messages/compat.py b/stored_messages/compat.py new file mode 100644 index 00000000..7bc3d2f1 --- /dev/null +++ b/stored_messages/compat.py @@ -0,0 +1,6 @@ +""" +The `compat` module provides support for backwards compatibility with older +versions of Django and Python. Please, add in this module the compatibility code. +""" + +from __future__ import unicode_literals diff --git a/stored_messages/constants.py b/stored_messages/constants.py new file mode 100644 index 00000000..9499c2eb --- /dev/null +++ b/stored_messages/constants.py @@ -0,0 +1,16 @@ +from django.contrib.messages.constants import * + + +STORED_DEBUG = DEBUG + 1 +STORED_INFO = INFO + 1 +STORED_SUCCESS = SUCCESS + 1 +STORED_WARNING = WARNING + 1 +STORED_ERROR = ERROR + 1 + +DEFAULT_TAGS.update({ + STORED_DEBUG: 'persisted debug', + STORED_INFO: 'persisted info', + STORED_SUCCESS: 'persisted success', + STORED_WARNING: 'persisted warning', + STORED_ERROR: 'persisted error', +}) diff --git a/stored_messages/migrations/0001_initial.py b/stored_messages/migrations/0001_initial.py new file mode 100644 index 00000000..78fb57ee --- /dev/null +++ b/stored_messages/migrations/0001_initial.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import django.utils.timezone + +from django.db import models, migrations +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Inbox', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ], + options={ + 'verbose_name_plural': 'inboxes', + }, + ), + migrations.CreateModel( + name='Message', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('message', models.TextField()), + ('level', models.IntegerField()), + ('tags', models.TextField()), + ('date', models.DateTimeField(default=django.utils.timezone.now)), + ], + ), + migrations.CreateModel( + name='MessageArchive', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('message', models.ForeignKey(to='stored_messages.Message', on_delete=models.CASCADE)), + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), + ], + ), + migrations.AddField( + model_name='inbox', + name='message', + field=models.ForeignKey(to='stored_messages.Message', on_delete=models.CASCADE), + ), + migrations.AddField( + model_name='inbox', + name='user', + field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE), + ), + ] diff --git a/stored_messages/migrations/0002_message_url.py b/stored_messages/migrations/0002_message_url.py new file mode 100644 index 00000000..807368c8 --- /dev/null +++ b/stored_messages/migrations/0002_message_url.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('stored_messages', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='message', + name='url', + field=models.URLField(blank=True, null=True), + ), + ] diff --git a/stored_messages/migrations/__init__.py b/stored_messages/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stored_messages/models.py b/stored_messages/models.py new file mode 100644 index 00000000..352715d1 --- /dev/null +++ b/stored_messages/models.py @@ -0,0 +1,58 @@ +from __future__ import unicode_literals + +from django.db import models +from django.conf import settings +from django.utils import timezone +from django.utils.translation import ugettext_lazy as _ + +from .settings import stored_messages_settings + + +class Message(models.Model): + """ + This model represents a message on the database. Fields are the same as in + `contrib.messages` + """ + message = models.TextField() + level = models.IntegerField() + tags = models.TextField() + url = models.URLField(null=True, blank=True) + date = models.DateTimeField(default=timezone.now) + + def __str__(self): + return self.message + + +class MessageArchive(models.Model): + """ + This model holds all the messages users received. Corresponding + database table will grow indefinitely depending on messages traffic. + """ + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + message = models.ForeignKey(Message, on_delete=models.CASCADE) + + def __str__(self): + return "[%s] %s" % (self.user, self.message) + + +class Inbox(models.Model): + """ + Inbox messages are stored in this model until users read them. Once read, + inbox messages are deleted. Inbox messages have an expire time, after + that they could be removed by a proper django command. We do not expect + database table corresponding to this model to grow much. + """ + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + message = models.ForeignKey(Message, on_delete=models.CASCADE) + + class Meta: + verbose_name_plural = _('inboxes') + + def expired(self): + expiration_date = self.message.date + timezone.timedelta( + days=stored_messages_settings.INBOX_EXPIRE_DAYS) + return expiration_date <= timezone.now() + expired.boolean = True # show a nifty icon in the admin + + def __str__(self): + return "[%s] %s" % (self.user, self.message) diff --git a/stored_messages/serializers.py b/stored_messages/serializers.py new file mode 100644 index 00000000..702a9f94 --- /dev/null +++ b/stored_messages/serializers.py @@ -0,0 +1,10 @@ +from rest_framework import serializers + + +class InboxSerializer(serializers.Serializer): + id = serializers.CharField() + message = serializers.CharField() + level = serializers.IntegerField() + tags = serializers.CharField() + date = serializers.DateTimeField(format=None) + url = serializers.URLField(required=False) diff --git a/stored_messages/settings.py b/stored_messages/settings.py new file mode 100644 index 00000000..cea53809 --- /dev/null +++ b/stored_messages/settings.py @@ -0,0 +1,130 @@ +""" +This module is largely inspired by django-rest-framework settings. + +Settings for the Stored Messages framework are all namespaced in the +STORED_MESSAGES setting. +For example your project's `settings.py` file might look like this:: + + STORED_MESSAGES = { + 'INBOX_EXPIRE_DAYS': 30, + } + +This module provides the `stored_messages_settings` object, that is used to +access Stored Messages framework settings, checking for user settings first, +then falling back to the defaults. +""" +from __future__ import unicode_literals + +import importlib + +from django.conf import settings + + +from .constants import * + + +USER_SETTINGS = getattr(settings, 'STORED_MESSAGES', None) +MESSAGE_TAGS = getattr(settings, 'MESSAGE_TAGS', {}) + +DEFAULTS = { + 'STORE_LEVELS': ( + STORED_DEBUG, + STORED_INFO, + STORED_SUCCESS, + STORED_WARNING, + STORED_ERROR, + ), + 'MESSAGE_TAGS': MESSAGE_TAGS.update({ + STORED_DEBUG: 'stored debug', + STORED_INFO: 'stored info', + STORED_SUCCESS: 'stored success', + STORED_WARNING: 'stored warning', + STORED_ERROR: 'stored error', + }), + 'INBOX_EXPIRE_DAYS': 30, + 'MESSAGE_EXPIRE_DAYS': 120, + 'STORAGE_BACKEND': 'stored_messages.backends.DefaultBackend', + # Only for Redis backend + 'REDIS_URL': 'redis://username:password@localhost:6379/0', +} + +# List of settings that cannot be empty +MANDATORY = ( + +) + +# List of settings that may be in string import notation. +IMPORT_STRINGS = ( + 'STORAGE_BACKEND', +) + + +def perform_import(val, setting_name): + """ + If the given setting is a string import notation, + then perform the necessary import or imports. + """ + if isinstance(val, str): + return import_from_string(val, setting_name) + elif isinstance(val, (list, tuple)): + return [import_from_string(item, setting_name) for item in val] + return val + + +def import_from_string(val, setting_name): + """ + Attempt to import a class from a string representation. + """ + try: + parts = val.split('.') + module_path, class_name = '.'.join(parts[:-1]), parts[-1] + module = importlib.import_module(module_path) + return getattr(module, class_name) + except ImportError as e: + msg = "Could not import '%s' for setting '%s'. %s: %s." % (val, setting_name, + e.__class__.__name__, e) + raise ImportError(msg) + + +class StoredMessagesSettings(object): + """ + A settings object, that allows settings to be accessed as properties. + + Any setting with string import paths will be automatically resolved + and return the class, rather than the string literal. + """ + + def __init__(self, user_settings=None, defaults=None, import_strings=None, mandatory=None): + self.user_settings = user_settings or {} + self.defaults = defaults or {} + self.import_strings = import_strings or () + self.mandatory = mandatory or () + + def __getattr__(self, attr): + if attr not in self.defaults.keys(): + raise AttributeError("Invalid Stored Messages setting: '%s'" % attr) + + try: + # Check if present in user settings + val = self.user_settings[attr] + except KeyError: + # Fall back to defaults + val = self.defaults[attr] + + # Coerce import strings into classes + if val and attr in self.import_strings: + val = perform_import(val, attr) + + self.validate_setting(attr, val) + + # Cache the result + setattr(self, attr, val) + return val + + def validate_setting(self, attr, val): + if not val and attr in self.mandatory: + raise AttributeError("Stored Messages setting: '%s' is mandatory" % attr) + + +stored_messages_settings = StoredMessagesSettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS, + MANDATORY) diff --git a/stored_messages/static/js/stored_messages.js b/stored_messages/static/js/stored_messages.js new file mode 100644 index 00000000..e69de29b diff --git a/stored_messages/storage.py b/stored_messages/storage.py new file mode 100644 index 00000000..c23bc3b9 --- /dev/null +++ b/stored_messages/storage.py @@ -0,0 +1,103 @@ +from django.contrib.messages.storage.fallback import FallbackStorage + +from .settings import stored_messages_settings +from .backends.exceptions import MessageTypeNotSupported + + +class StorageMixin(object): + """ + This mixin implements a message storage compliant with + `django.contrib.messages` which stores messages on the database when + needed, delegating to the sibling class otherwise. Messages are stored + only when the user is authenticated and the message level is configured + for being persisted. The mixin can be derived together with another class + implementing a storage, tipically one of the three provided by Django out + of the box. + """ + def __init__(self, request, *args, **kwargs): + self.user = request.user + BackendClass = stored_messages_settings.STORAGE_BACKEND + self.backend = BackendClass() + super(StorageMixin, self).__init__(request, *args, **kwargs) + + def _get(self, *args, **kwargs): + """ + Retrieve unread messages for current user, both from the inbox and + from other storages + """ + messages, all_retrieved = super(StorageMixin, self)._get(*args, **kwargs) + if self.user.is_authenticated(): + inbox_messages = self.backend.inbox_list(self.user) + else: + inbox_messages = [] + + return messages + inbox_messages, all_retrieved + + def add(self, level, message, extra_tags=''): + """ + If the message level was configured for being stored and request.user + is not anonymous, save it to the database. Otherwise, let some other + class handle the message. + + Notice: controls like checking the message is not empty and the level + is above the filter need to be performed here, but it could happen + they'll be performed again later if the message does not need to be + stored. + """ + if not message: + return + # Check that the message level is not less than the recording level. + level = int(level) + if level < self.level: + return + # Check if the message doesn't have a level that needs to be persisted + if level not in stored_messages_settings.STORE_LEVELS or self.user.is_anonymous(): + return super(StorageMixin, self).add(level, message, extra_tags) + + self.added_new = True + m = self.backend.create_message(level, message, extra_tags) + self.backend.archive_store([self.user], m) + self._queued_messages.append(m) + + def _store(self, messages, response, *args, **kwargs): + """ + persistent messages are already in the database inside the 'archive', + so we can say they're already "stored". + Here we put them in the inbox, or remove from the inbox in case the + messages were iterated. + + messages contains only new msgs if self.used==True + else contains both new and unread messages + """ + contrib_messages = [] + if self.user.is_authenticated(): + if not messages: + # erase inbox + self.backend.inbox_purge(self.user) + else: + for m in messages: + try: + self.backend.inbox_store([self.user], m) + except MessageTypeNotSupported: + contrib_messages.append(m) + + super(StorageMixin, self)._store(contrib_messages, response, *args, **kwargs) + + def _prepare_messages(self, messages): + """ + Like the base class method, prepares a list of messages for storage + but avoid to do this for `models.Message` instances. + """ + for message in messages: + if not self.backend.can_handle(message): + message._prepare() + + +class PersistentStorage(StorageMixin, FallbackStorage): + """ + This class is provided for convenience: it implements `StorageMixin` which + persists messages having a type configured to be persisted and passes + other messages to `FallbackStorage`, one of the defaults provided by + Django which stores messages inside cookies or sessions. + """ + pass diff --git a/stored_messages/templates/stored_messages/base.html b/stored_messages/templates/stored_messages/base.html new file mode 100644 index 00000000..09d26f38 --- /dev/null +++ b/stored_messages/templates/stored_messages/base.html @@ -0,0 +1,21 @@ + +{% comment %} +As the developer of this package, don't place anything here if you can help it +since this allows developers to have interoperability between your template +structure and their own. + +Example: Developer melding the 2SoD pattern to fit inside with another pattern:: + + {% extends "base.html" %} + {% load static %} + + + {% block extra_js %} + + + {% block javascript %} + + {% endblock javascript %} + + {% endblock extra_js %} +{% endcomment %} diff --git a/stored_messages/templates/stored_messages/stored_messages_list.html b/stored_messages/templates/stored_messages/stored_messages_list.html new file mode 100644 index 00000000..ec695c8f --- /dev/null +++ b/stored_messages/templates/stored_messages/stored_messages_list.html @@ -0,0 +1,7 @@ +{% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    +{% endif %} \ No newline at end of file diff --git a/stored_messages/templatetags/__init__.py b/stored_messages/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/stored_messages/templatetags/stored_messages_tags.py b/stored_messages/templatetags/stored_messages_tags.py new file mode 100644 index 00000000..49271694 --- /dev/null +++ b/stored_messages/templatetags/stored_messages_tags.py @@ -0,0 +1,49 @@ +from __future__ import unicode_literals + +from django import template + +from ..models import Inbox, MessageArchive + + +register = template.Library() + + +@register.inclusion_tag("stored_messages/stored_messages_list.html", takes_context=True) +def stored_messages_list(context, num_elements=10): + """ + Renders a list of unread stored messages for the current user + """ + if "user" in context: + user = context["user"] + if user.is_authenticated(): + qs = Inbox.objects.select_related("message").filter(user=user) + return { + "messages": qs[:num_elements], + "count": qs.count(), + } + + +@register.simple_tag(takes_context=True) +def stored_messages_count(context): + """ + Renders a list of unread stored messages for the current user + """ + if "user" in context: + user = context["user"] + if user.is_authenticated(): + return Inbox.objects.select_related("message").filter(user=user).count() + + +@register.inclusion_tag("stored_messages/stored_messages_list.html", takes_context=True) +def stored_messages_archive(context, num_elements=10): + """ + Renders a list of archived messages for the current user + """ + if "user" in context: + user = context["user"] + if user.is_authenticated(): + qs = MessageArchive.objects.select_related("message").filter(user=user) + return { + "messages": qs[:num_elements], + "count": qs.count(), + } diff --git a/stored_messages/urls.py b/stored_messages/urls.py new file mode 100644 index 00000000..7b40d2eb --- /dev/null +++ b/stored_messages/urls.py @@ -0,0 +1,17 @@ +""" +At the moment this module does something only when restframework is available +""" +from django.conf import settings + +if 'rest_framework' in settings.INSTALLED_APPS: + from rest_framework.routers import DefaultRouter + from django.conf.urls import url, include + from . import views + + router = DefaultRouter() + router.register(r'inbox', views.InboxViewSet, base_name='inbox') + + urlpatterns = [ + url(r'^', include(router.urls)), + url(r'^mark_all_read/$', views.mark_all_read, name='mark_all_read'), + ] diff --git a/stored_messages/views.py b/stored_messages/views.py new file mode 100644 index 00000000..c9979327 --- /dev/null +++ b/stored_messages/views.py @@ -0,0 +1,60 @@ +from rest_framework import viewsets +from rest_framework.decorators import api_view, detail_route +from rest_framework.response import Response + +from django.contrib.auth.decorators import login_required + +from .serializers import InboxSerializer +from .backends.exceptions import MessageDoesNotExist + + +class InboxViewSet(viewsets.ViewSet): + """ + Provides `list` and `detail` actions, plus a `read` POST endpoint for + marking inbox messages as read. + """ + def list(self, request): + from .settings import stored_messages_settings + backend = stored_messages_settings.STORAGE_BACKEND() + messages = backend.inbox_list(request.user) + serializer = InboxSerializer(messages, many=True) + return Response(serializer.data) + + def retrieve(self, request, pk=None): + from .settings import stored_messages_settings + backend = stored_messages_settings.STORAGE_BACKEND() + + try: + msg = backend.inbox_get(request.user, pk) + except MessageDoesNotExist as e: + return Response(e.message, status='404') + + serializer = InboxSerializer(msg, many=False) + return Response(serializer.data) + + @detail_route(methods=['POST']) + def read(self, request, pk=None): + """ + Mark the message as read (i.e. delete from inbox) + """ + from .settings import stored_messages_settings + backend = stored_messages_settings.STORAGE_BACKEND() + + try: + backend.inbox_delete(request.user, pk) + except MessageDoesNotExist as e: + return Response(e.message, status='404') + + return Response({'status': 'message marked as read'}) + + +@login_required +@api_view(['POST']) +def mark_all_read(request): + """ + Mark all messages as read (i.e. delete from inbox) for current logged in user + """ + from .settings import stored_messages_settings + backend = stored_messages_settings.STORAGE_BACKEND() + backend.inbox_purge(request.user) + return Response({"message": "All messages read"}) From 7cbbbc0a3a36b9a45bec5d62a57af9ef3787426d Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 10:32:20 +0300 Subject: [PATCH 33/76] Implement CMSMultiSiteMiddleware in the repo instead of the unmaintained fork --- dynamicweb/settings/base.py | 1 - utils/middleware.py | 40 ++++++++++++++++++++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 96c254e3..9593b51d 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -102,7 +102,6 @@ INSTALLED_APPS = ( 'django.contrib.staticfiles', 'django.contrib.sites', 'multisite', - 'djangocms_multisite', 'easy_thumbnails', 'utils', 'stored_messages', diff --git a/utils/middleware.py b/utils/middleware.py index 103ae3d6..4647251b 100644 --- a/utils/middleware.py +++ b/utils/middleware.py @@ -3,13 +3,47 @@ import re from django.conf import settings from django.http import HttpResponse +from django.urls import set_urlconf +from django.utils.cache import patch_vary_headers from django.utils.deprecation import MiddlewareMixin +from urllib import parse as urlparse -from djangocms_multisite.middleware import CMSMultiSiteMiddleware as _CMSMultiSite +from cms.utils.apphook_reload import reload_urlconf -class CMSMultiSiteMiddleware(MiddlewareMixin, _CMSMultiSite): - """The package's middleware is old-style (no get_response); adapt it""" +class CMSMultiSiteMiddleware(MiddlewareMixin): + """ + Picks the urlconf for the requested host (MULTISITE_CMS_URLS, with + MULTISITE_CMS_ALIASES and MULTISITE_CMS_FALLBACK). Derived from + djangocms-multisite (BSD, Copyright (c) 2013, Iacopo Spalletti), whose + ungleich fork is no longer installed. + """ + + def process_request(self, request): + urls = getattr(settings, 'MULTISITE_CMS_URLS', {}) + aliases = getattr(settings, 'MULTISITE_CMS_ALIASES', {}) + fallback = getattr(settings, 'MULTISITE_CMS_FALLBACK', '') + host = urlparse.urlparse( + request.build_absolute_uri()).hostname.split(':')[0] + urlconf = urls.get(host) + if not urlconf: + for domain, hosts in aliases.items(): + if host in hosts and domain in urls: + urlconf = urls[domain] + break + if not urlconf and fallback in urls: + urlconf = urls[fallback] + if urlconf: + request.urlconf = urlconf + # for code that does not know about the request (get_absolute_url()) + set_urlconf(urlconf) + reload_urlconf() + + def process_response(self, request, response): + patch_vary_headers(response, ('Host',)) + # back to the default urlconf + set_urlconf(None) + return response class MultipleProxyMiddleware(MiddlewareMixin): From f20e7577773fc33bc4fa040b7a83db526a0aebf6 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 10:32:20 +0300 Subject: [PATCH 34/76] Drop custom view_* permissions that clash with Django's built-in ones --- .../0067_drop_custom_view_permissions.py | 15 +++++++ hosting/models.py | 45 +++++-------------- 2 files changed, 25 insertions(+), 35 deletions(-) create mode 100644 hosting/migrations/0067_drop_custom_view_permissions.py diff --git a/hosting/migrations/0067_drop_custom_view_permissions.py b/hosting/migrations/0067_drop_custom_view_permissions.py new file mode 100644 index 00000000..7cf16094 --- /dev/null +++ b/hosting/migrations/0067_drop_custom_view_permissions.py @@ -0,0 +1,15 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + """Django 2.1+ creates view_* permissions itself; the custom ones clash.""" + + dependencies = [ + ('hosting', '0066_auto_20230727_0812'), + ] + + operations = [ + migrations.AlterModelOptions(name=name, options={}) + for name in ('hostingorder', 'hostingbill', 'monthlyhostingbill', + 'hostingbilllineitem', 'usercarddetail') + ] diff --git a/hosting/models.py b/hosting/models.py index 12edba65..306061a2 100644 --- a/hosting/models.py +++ b/hosting/models.py @@ -104,8 +104,8 @@ class HostingOrder(AssignPermissionsMixin, models.Model): ORDER_DECLINED_STATUS = 'Declined' vm_id = models.IntegerField(default=0) - customer = models.ForeignKey(StripeCustomer) - billing_address = models.ForeignKey(BillingAddress) + customer = models.ForeignKey(StripeCustomer, on_delete=models.CASCADE) + billing_address = models.ForeignKey(BillingAddress, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) approved = models.BooleanField(default=False) last4 = models.CharField(max_length=4) @@ -113,7 +113,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model): stripe_charge_id = models.CharField(max_length=100, null=True) price = models.FloatField() subscription_id = models.CharField(max_length=100, null=True) - vm_pricing = models.ForeignKey(VMPricing) + vm_pricing = models.ForeignKey(VMPricing, on_delete=models.CASCADE) order_detail = models.ForeignKey( OrderDetail, null=True, blank=True, default=None, on_delete=models.SET_NULL @@ -127,11 +127,6 @@ class HostingOrder(AssignPermissionsMixin, models.Model): ) permissions = ('view_hostingorder',) - class Meta: - permissions = ( - ('view_hostingorder', 'View Hosting Order'), - ) - def __str__(self): hosting_order_str = ("Order Nr: #{} - VM_ID: {} - {} - {} - " "Specs: {} - Price: {}").format( @@ -202,7 +197,7 @@ class HostingOrder(AssignPermissionsMixin, models.Model): class UserHostingKey(models.Model): - user = models.ForeignKey(CustomUser, blank=True, null=True) + user = models.ForeignKey(CustomUser, blank=True, null=True, on_delete=models.CASCADE) public_key = models.TextField() private_key = models.FileField(upload_to='private_keys', blank=True) created_at = models.DateTimeField(auto_now_add=True) @@ -238,17 +233,12 @@ class UserHostingKey(models.Model): class HostingBill(AssignPermissionsMixin, models.Model): - customer = models.ForeignKey(StripeCustomer) - billing_address = models.ForeignKey(BillingAddress) + customer = models.ForeignKey(StripeCustomer, on_delete=models.CASCADE) + billing_address = models.ForeignKey(BillingAddress, on_delete=models.CASCADE) total_price = models.FloatField(default=0.0) permissions = ('view_hostingbill',) - class Meta: - permissions = ( - ('view_hostingbill', 'View Hosting Bill'), - ) - def __str__(self): return "%s" % (self.customer.user.email) @@ -263,8 +253,8 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model): """ Corresponds to Invoice object of Stripe """ - customer = models.ForeignKey(StripeCustomer) - order = models.ForeignKey(HostingOrder) + customer = models.ForeignKey(StripeCustomer, on_delete=models.CASCADE) + order = models.ForeignKey(HostingOrder, on_delete=models.CASCADE) created = models.DateTimeField(help_text="When the invoice was created") receipt_number = models.CharField( help_text="The receipt number that is generated on Stripe", @@ -287,11 +277,6 @@ class MonthlyHostingBill(AssignPermissionsMixin, models.Model): permissions = ('view_monthlyhostingbill',) - class Meta: - permissions = ( - ('view_monthlyhostingbill', 'View Monthly Hosting'), - ) - @classmethod def create(cls, args): # Try to infer the HostingOrder from subscription id or VM_ID @@ -498,11 +483,6 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model): unit_amount = models.PositiveIntegerField() permissions = ('view_hostingbilllineitem',) - class Meta: - permissions = ( - ('view_hostingbilllineitem', 'View Monthly Hosting Bill Line Item'), - ) - def amount_in_chf(self): """ Returns amount in chf. The amount in this model is in cents (as in @@ -554,7 +534,7 @@ class HostingBillLineItem(AssignPermissionsMixin, models.Model): class VMDetail(models.Model): - user = models.ForeignKey(CustomUser) + user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) vm_id = models.IntegerField(default=0) disk_size = models.FloatField(default=0.0) cores = models.FloatField(default=0.0) @@ -574,7 +554,7 @@ class VMDetail(models.Model): class UserCardDetail(AssignPermissionsMixin, models.Model): permissions = ('view_usercarddetail',) - stripe_customer = models.ForeignKey(StripeCustomer) + stripe_customer = models.ForeignKey(StripeCustomer, on_delete=models.CASCADE) last4 = models.CharField(max_length=4) brand = models.CharField(max_length=128) card_id = models.CharField(max_length=100, blank=True, default='') @@ -583,11 +563,6 @@ class UserCardDetail(AssignPermissionsMixin, models.Model): exp_year = models.IntegerField(null=False) preferred = models.BooleanField(default=False) - class Meta: - permissions = ( - ('view_usercarddetail', 'View User Card'), - ) - @classmethod def create(cls, stripe_customer=None, last4=None, brand=None, fingerprint=None, exp_month=None, exp_year=None, card_id=None, From a54ebfd749d528dc2bd14af3e7d6f1fe8d85e864 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 10:32:20 +0300 Subject: [PATCH 35/76] Move to Python 3.8 and Django 2.2 dependency pins --- Dockerfile | 3 +- requirements.txt | 176 +++++++++++++++++++++++------------------------ 2 files changed, 89 insertions(+), 90 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4e41e75e..c02cf53c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # FROM python:3.10.0-alpine3.15 -FROM python:3.5-alpine3.12 +FROM python:3.8-alpine3.12 WORKDIR /usr/src/app @@ -14,6 +14,7 @@ RUN apk add --update --no-cache \ libxslt-dev \ libmemcached-dev \ zlib-dev \ + libffi-dev \ && rm -rf /var/cache/apk/* ## For alpine 3.15 replace postgresql-dev with libpq-dev diff --git a/requirements.txt b/requirements.txt index bff8290c..7bc874a2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,118 +1,116 @@ -Babel==2.7.0 -Django==1.11.29 -Django-Select2==6.3.1 -Pillow==6.0.0 +Django==2.2.28 +Babel==2.8.0 +Django-Select2==7.4.2 +Pillow==7.2.0 URLObject==2.4.3 -Unidecode==1.0.23 +Unidecode==1.1.1 YURL==1.0.0 -aldryn-apphooks-config==0.5.2 +aldryn-apphooks-config==0.6.0 aldryn-boilerplates==0.8.0 aldryn-categories==1.2.0 aldryn-common==1.0.5 aldryn-newsblog==2.2.1 aldryn-people==2.2.0 -aldryn-search==1.0.1 +aldryn-search==1.1.0 aldryn-translation-tools==0.3.0 backport-collections==0.1 -cmsplugin-filer==1.1.3 -django-appconf==1.0.3 -django-appdata==0.2.1 -django-bootstrap3==11.0.0 -django-classy-tags==0.9.0 -django-cms==3.6.0 -django-compressor==2.3 -django-debug-toolbar==2.0 -python-dotenv==0.10.3 -django-extensions==2.1.9 -django-filer==1.5.0 -django-filter==2.1.0 -django-formtools==2.1 -django-guardian==2.0.0 +django-appconf==1.0.4 +django-appdata==0.3.0 +django-bootstrap3==14.0.0 +django-classy-tags==1.0.0 +django-cms==3.7.3 +django-compressor==2.4 +django-debug-toolbar==2.2 +python-dotenv==0.13.0 +django-extensions==3.0.1 +django-filer==1.7.1 +django-filter==2.3.0 +django-formtools==2.2 +django-guardian==2.3.0 django-haystack==2.8.1 -django-meta==1.5.1 -django-model-utils==3.2.0 -django-mptt==0.10.0 -django-parler==1.9.2 +django-meta==1.6.1 +django-model-utils==4.0.0 +django-mptt==0.11.0 +django-parler==2.0.1 django-phonenumber-field==1.3.0 -django-polymorphic==2.0.3 -django-reversion==2.0.13 -django-sekizai==1.0.0 -django-sortedm2m==1.5.0 -django-spurl==0.6.6 +django-polymorphic==2.1.2 +django-reversion==3.0.7 +django-sekizai==1.1.0 +django-sortedm2m==3.0.0 +django-spurl==0.6.7 django-standard-form==1.1.1 -django-stored-messages==1.4.0 -django-taggit==0.23.0 -django-taggit-autosuggest==0.3.7 +django-taggit==1.2.0 +django-taggit-autosuggest==0.3.8 django-taggit-templatetags==0.2.5 django-templatetag-sugar==1.0 -django-treebeard==4.3 -djangocms-admin-style==1.4.0 -djangocms-apphook-setup==0.3.0 -djangocms-blog==0.9.9 -djangocms-file==2.3.0 +django-treebeard==4.3.1 +djangocms-admin-style==1.5.0 +djangocms-apphook-setup==0.4.1 +djangocms-blog==1.1.1 +djangocms-file==2.4.0 djangocms-flash==0.3.0 -djangocms-googlemap==1.3.0 +djangocms-googlemap==1.4.0 djangocms-inherit==0.2.2 -djangocms-link==2.4.0 -djangocms-page-meta==0.8.5 -djangocms-picture==2.2.1 +djangocms-link==2.6.1 +djangocms-page-meta==0.10.0 +djangocms-picture==2.4.0 djangocms-teaser==0.2.0 -djangocms-text-ckeditor==3.8.0 -djangocms-video==2.1.1 -easy-thumbnails==2.6 -html5lib==1.0.1 -ldap3==2.6 -lxml==4.3.4 -model-mommy==1.6.0 -phonenumbers==8.10.14 -phonenumberslite==8.10.14 -psycopg2==2.7.7 -pycryptodome==3.8.2 -pylibmc==1.6.0 -python-dateutil==2.8.0 -python-slugify==3.0.2 -pytz==2019.1 +djangocms-text-ckeditor==3.9.1 +djangocms-video==2.3.0 +easy-thumbnails==2.7 +html5lib==1.1 +ldap3==2.7 +lxml==4.5.1 +model-mommy==2.0.0 +phonenumbers==8.12.6 +phonenumberslite==8.12.6 +psycopg2==2.8.5 +pycryptodome==3.9.8 +pylibmc==1.6.1 +python-dateutil==2.8.1 +python-slugify==4.0.1 +pytz==2020.1 rcssmin==1.0.6 -requests==2.22.0 +requests==2.24.0 rjsmin==1.1.0 -six==1.12.0 -sqlparse==0.3.0 -stripe==2.32.0 -wheel==0.33.4 +six==1.15.0 +sqlparse==0.3.1 +stripe==2.48.0 +wheel==0.34.2 django-admin-honeypot==1.1.0 -coverage==4.5.3 -djangorestframework==3.9.4 -flake8==3.7.7 +coverage==5.1 +djangorestframework==3.11.0 +flake8==3.8.3 python-memcached==1.59 -celery==4.3.0 -redis==3.2.1 -django-celery-results==1.1.2 -kombu==4.6.3 +celery==4.4.6 +redis==3.5.3 +django-celery-results==1.2.1 +kombu==4.6.11 mccabe==0.6.1 -pycodestyle==2.5.0 -pyflakes==2.1.1 -billiard==3.6.0.0 -amqp==2.5.0 +pycodestyle==2.6.0 +pyflakes==2.2.0 +billiard==3.6.3.0 +amqp==2.6.0 vine==1.3.0 -cdist==5.1.2 -pyotp==2.2.7 -django-recaptcha==2.0.4 -git+https://github.com/ungleich/python-oca.git#egg=oca -git+https://github.com/ungleich/djangocms-multisite.git#egg=djangocms_multisite -# transitive dependencies, pinned for reproducible builds -certifi==2021.10.8 +cdist==6.6.0 +pyotp==2.3.0 +django-recaptcha==2.0.6 +certifi==2020.6.20 chardet==3.0.4 -django-js-asset==1.2.3 -django-multisite==1.9.0 +django-js-asset==1.2.2 djangocms-attributes-field==1.2.0 entrypoints==0.3 -idna==2.8 -importlib-metadata==2.1.3 +idna==2.10 +importlib-metadata==1.7.0 pluggy==0.13.1 pyasn1==0.4.8 -requests-file==3.0.1 -text-unidecode==1.2 -tldextract==2.2.3 -urllib3==1.25.11 +requests-file==1.5.1 +text-unidecode==1.3 +tldextract==2.2.2 +urllib3==1.25.9 webencodings==0.5.1 -zipp==1.2.0 +zipp==3.1.0 +git+https://github.com/ungleich/python-oca.git#egg=oca +# transitive dependencies, pinned for reproducible builds +django-multisite==1.9.0 +future==1.0.0 From aebf0654490847b2f37080e396431bb0e89e7d3d Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 10:32:20 +0300 Subject: [PATCH 36/76] Bring legacy django-reversion tables to the 3.x schema on start --- start-web.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/start-web.sh b/start-web.sh index 136c4bbd..611c0ba0 100644 --- a/start-web.sh +++ b/start-web.sh @@ -29,6 +29,23 @@ try: call_command('migrate', 'cmsplugin_filer_image', interactive=False, skip_checks=True) finally: MigrationLoader.check_consistent_history = check +# django-reversion 1.x tables: 3.x only ships one squashed migration that would +# re-create them, so bring the old tables to its schema and record it as applied. +squash = '0001_squashed_0004_auto_20160611_1202' +if (recorder.migration_qs.filter(app='reversion', name='0002_auto_20141216_1509').exists() + and not recorder.migration_qs.filter(app='reversion', name=squash).exists()): + with connection.cursor() as cursor: + for sql in ( + 'ALTER TABLE reversion_revision DROP COLUMN manager_slug', + 'ALTER TABLE reversion_version DROP COLUMN object_id_int', + 'ALTER TABLE reversion_version ALTER COLUMN object_id TYPE varchar(191)', + "ALTER TABLE reversion_version ADD COLUMN db varchar(191) NOT NULL DEFAULT 'default'", + 'ALTER TABLE reversion_version ALTER COLUMN db DROP DEFAULT', + 'ALTER TABLE reversion_version ADD CONSTRAINT reversion_version_db_unique ' + 'UNIQUE (db, content_type_id, object_id, revision_id)', + ): + cursor.execute(sql) + recorder.record_applied('reversion', squash) call_command('migrate', interactive=False, skip_checks=True) from django.contrib.sites.models import Site From 9c4c22339020a805536a122f217aada07850b705 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 10:32:20 +0300 Subject: [PATCH 37/76] Adapt code to Django 2.2: urls, on_delete, app_name, auth views, template tags --- alplora/urls.py | 2 ++ datacenterlight/cms_models.py | 5 +++-- datacenterlight/tasks.py | 2 +- datacenterlight/templatetags/custom_tags.py | 2 +- datacenterlight/urls.py | 2 ++ datacenterlight/views.py | 2 +- digitalglarus/admin.py | 2 +- digitalglarus/mixins.py | 6 +++--- digitalglarus/models.py | 18 +++++++++--------- digitalglarus/test_views.py | 4 ++-- digitalglarus/urls.py | 6 ++++-- digitalglarus/views.py | 2 +- dynamicweb/urls.py | 6 +++--- dynamicweb/urls_multi.py | 2 +- hosting/mixins.py | 2 +- hosting/test_views.py | 4 ++-- hosting/urls.py | 6 ++++-- hosting/views.py | 2 +- membership/migrations/0001_initial.py | 2 +- membership/models.py | 8 ++++---- membership/tests.py | 2 +- nosystemd/admin.py | 2 +- nosystemd/models.py | 6 +++--- nosystemd/urls.py | 6 ++++-- nosystemd/views.py | 2 +- opennebula_api/urls.py | 2 ++ ungleich/migrations/0001_initial.py | 4 ++-- ungleich/migrations/0002_ungleichpage_image.py | 2 +- ungleich/models.py | 3 ++- ungleich/test_views.py | 4 ++-- ungleich/urls.py | 2 ++ ungleich_page/test_views.py | 4 ++-- ungleich_page/urls.py | 2 ++ ungleich_page/views.py | 2 +- utils/models.py | 2 +- utils/views.py | 2 +- 36 files changed, 75 insertions(+), 57 deletions(-) diff --git a/alplora/urls.py b/alplora/urls.py index 058f5499..8696925d 100644 --- a/alplora/urls.py +++ b/alplora/urls.py @@ -3,6 +3,8 @@ from django.conf.urls import url from .views import IndexView, LoginView, ContactView +app_name = 'alplora' + urlpatterns = [ url(r'^$', IndexView.as_view(), name='index'), url(r'login/', LoginView.as_view(), name='login'), diff --git a/datacenterlight/cms_models.py b/datacenterlight/cms_models.py index d1c5c259..f45a7e61 100644 --- a/datacenterlight/cms_models.py +++ b/datacenterlight/cms_models.py @@ -33,7 +33,7 @@ class CMSIntegration(models.Model): 'datacenterlight_calculator', related_name='dcl-calculator-placeholder+' ) - domain = models.ForeignKey(Site, null=True, blank=True) + domain = models.ForeignKey(Site, null=True, blank=True, on_delete=models.CASCADE) class Meta: unique_together = ('name', 'domain') @@ -43,7 +43,7 @@ class CMSIntegration(models.Model): class CMSFaviconExtension(PageExtension): - favicon = FilerFileField(related_name="cms_favicon_image") + favicon = FilerFileField(related_name="cms_favicon_image", on_delete=models.CASCADE) extension_pool.register(CMSFaviconExtension) @@ -340,6 +340,7 @@ class DCLCalculatorPluginModel(CMSPlugin): related_name="dcl_custom_pricing_vm_pricing", help_text='Choose a pricing that will be associated with this ' 'Calculator' + , on_delete=models.CASCADE ) vm_type = models.CharField( max_length=50, choices=VMTemplate.VM_TYPE_CHOICES, diff --git a/datacenterlight/tasks.py b/datacenterlight/tasks.py index 899b506f..9650af92 100644 --- a/datacenterlight/tasks.py +++ b/datacenterlight/tasks.py @@ -5,7 +5,7 @@ from celery.exceptions import MaxRetriesExceededError from celery.utils.log import get_task_logger from django.conf import settings from django.core.mail import EmailMessage -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils import translation from django.utils.translation import ugettext_lazy as _ diff --git a/datacenterlight/templatetags/custom_tags.py b/datacenterlight/templatetags/custom_tags.py index fc39eec7..7f374039 100644 --- a/datacenterlight/templatetags/custom_tags.py +++ b/datacenterlight/templatetags/custom_tags.py @@ -2,7 +2,7 @@ import datetime import logging from django import template -from django.core.urlresolvers import resolve, reverse +from django.urls import resolve, reverse from django.utils.safestring import mark_safe from django.utils.translation import activate, get_language, ugettext_lazy as _ diff --git a/datacenterlight/urls.py b/datacenterlight/urls.py index 13296de7..6aa56d83 100644 --- a/datacenterlight/urls.py +++ b/datacenterlight/urls.py @@ -7,6 +7,8 @@ from .views import ( WhyDataCenterLightView, ContactUsView ) +app_name = 'datacenterlight' + urlpatterns = [ url(r'^$', IndexView.as_view(), name='index'), url(r'^t/$', IndexView.as_view(), name='index_t'), diff --git a/datacenterlight/views.py b/datacenterlight/views.py index c708ff6a..7a5a3cd9 100644 --- a/datacenterlight/views.py +++ b/datacenterlight/views.py @@ -7,7 +7,7 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth import login, authenticate from django.core.exceptions import ValidationError -from django.core.urlresolvers import reverse +from django.urls import reverse from django.http import ( HttpResponseRedirect, JsonResponse, Http404, HttpResponse ) diff --git a/digitalglarus/admin.py b/digitalglarus/admin.py index c887e8d2..cd419d09 100644 --- a/digitalglarus/admin.py +++ b/digitalglarus/admin.py @@ -2,7 +2,7 @@ from django.contrib import admin from .models import DGGallery, DGPicture, Booking, BookingPrice,\ MembershipOrder, Membership, MembershipType, BookingOrder, BookingCancellation -from django.core.urlresolvers import reverse +from django.urls import reverse from utils.models import ContactMessage from django.utils.html import format_html diff --git a/digitalglarus/mixins.py b/digitalglarus/mixins.py index 18a02239..55372701 100644 --- a/digitalglarus/mixins.py +++ b/digitalglarus/mixins.py @@ -1,4 +1,4 @@ -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import UpdateView from django.db import models @@ -33,9 +33,9 @@ class IsNotMemberMixin(object): class Ordereable(models.Model): - customer = models.ForeignKey(StripeCustomer) + customer = models.ForeignKey(StripeCustomer, on_delete=models.CASCADE) amount = models.FloatField() - billing_address = models.ForeignKey(BillingAddress) + billing_address = models.ForeignKey(BillingAddress, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) approved = models.BooleanField(default=False) last4 = models.CharField(max_length=4, blank=True) diff --git a/digitalglarus/models.py b/digitalglarus/models.py index fc4289aa..951f545d 100644 --- a/digitalglarus/models.py +++ b/digitalglarus/models.py @@ -7,7 +7,7 @@ from django.db import models from django.db.models import Q from cms.models import CMSPlugin from filer.fields.image import FilerImageField -from django.core.urlresolvers import reverse +from django.urls import reverse from django.utils.functional import cached_property from .mixins import Ordereable @@ -72,7 +72,7 @@ class MembershipType(models.Model): class Membership(models.Model): - type = models.ForeignKey(MembershipType) + type = models.ForeignKey(MembershipType, on_delete=models.CASCADE) active = models.BooleanField(default=True) start_date = models.DateField() end_date = models.DateField() @@ -141,7 +141,7 @@ class Membership(models.Model): class MembershipOrder(Ordereable, models.Model): - membership = models.ForeignKey(Membership) + membership = models.ForeignKey(Membership, on_delete=models.CASCADE) start_date = models.DateField() end_date = models.DateField() stripe_subscription_id = models.CharField(max_length=100, null=True) @@ -268,7 +268,7 @@ class BookingOrder(Ordereable, models.Model): (CANCELLED, 'Cancelled') ) - booking = models.OneToOneField(Booking) + booking = models.OneToOneField(Booking, on_delete=models.CASCADE) original_price = models.FloatField() special_month_price = models.FloatField() status = models.PositiveIntegerField(choices=STATUS_CHOICES, default=1) @@ -297,7 +297,7 @@ class BookingOrder(Ordereable, models.Model): class BookingCancellation(models.Model): - order = models.ForeignKey(BookingOrder) + order = models.ForeignKey(BookingOrder, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now=True) required_refund = models.BooleanField(default=True) refund = models.BooleanField(default=False) @@ -323,7 +323,7 @@ class Supporter(models.Model): class DGGallery(models.Model): - parent = models.ForeignKey('self', blank=True, null=True) + parent = models.ForeignKey('self', blank=True, null=True, on_delete=models.CASCADE) name = models.CharField(max_length=30) def __str__(self): @@ -337,8 +337,8 @@ class DGGallery(models.Model): class DGPicture(models.Model): - gallery = models.ForeignKey(DGGallery) - image = FilerImageField(related_name='dg_gallery') + gallery = models.ForeignKey(DGGallery, on_delete=models.CASCADE) + image = FilerImageField(related_name='dg_gallery', on_delete=models.CASCADE) description = models.CharField(max_length=60) def __str__(self): @@ -346,7 +346,7 @@ class DGPicture(models.Model): class DGGalleryPlugin(CMSPlugin): - dgGallery = models.ForeignKey(DGGallery) + dgGallery = models.ForeignKey(DGGallery, on_delete=models.CASCADE) class DGSupportersPlugin(CMSPlugin): diff --git a/digitalglarus/test_views.py b/digitalglarus/test_views.py index aff11081..db1d03b2 100644 --- a/digitalglarus/test_views.py +++ b/digitalglarus/test_views.py @@ -3,8 +3,8 @@ from unittest import mock, skipIf from django.test import TestCase from django.conf import settings -from django.core.urlresolvers import reverse -from django.core.urlresolvers import resolve +from django.urls import reverse +from django.urls import resolve from cms.test_utils.testcases import CMSTestCase from django.contrib.auth.tokens import default_token_generator from django.utils.http import urlsafe_base64_encode diff --git a/digitalglarus/urls.py b/digitalglarus/urls.py index 64a63303..f3bba898 100644 --- a/digitalglarus/urls.py +++ b/digitalglarus/urls.py @@ -13,6 +13,8 @@ from .views import ContactView, IndexView, HistoryView, LoginView, SignupView,\ # from membership.views import LoginRegistrationView +app_name = 'digitalglarus' + urlpatterns = [ url(_(r'booking/payment/edit/?$'), EditCreditCardView.as_view(), name='edit_credit_card'), @@ -22,8 +24,8 @@ urlpatterns = [ url(_(r'contact/?$'), ContactView.as_view(), name='contact'), url(_(r'login/?$'), LoginView.as_view(), name='login'), url(_(r'signup/?$'), SignupView.as_view(), name='signup'), - url(r'^logout/?$', auth_views.logout, - {'next_page': '/digitalglarus/login?logged_out=true'}, name='logout'), + url(r'^logout/?$', auth_views.LogoutView.as_view( + next_page='/digitalglarus/login?logged_out=true'), name='logout'), url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'), url(r'reset-password-confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', PasswordResetConfirmView.as_view(), name='reset_password_confirm'), diff --git a/digitalglarus/views.py b/digitalglarus/views.py index bf3ed2c6..3603dd55 100644 --- a/digitalglarus/views.py +++ b/digitalglarus/views.py @@ -3,7 +3,7 @@ import logging from django.conf import settings from django.shortcuts import render from django.http import HttpResponseRedirect, Http404 -from django.core.urlresolvers import reverse_lazy, reverse +from django.urls import reverse_lazy, reverse from django.utils.translation import ugettext_lazy as _ from django.views.generic import TemplateView, UpdateView from django.contrib.auth.mixins import LoginRequiredMixin diff --git a/dynamicweb/urls.py b/dynamicweb/urls.py index b9a02981..9dd257c5 100644 --- a/dynamicweb/urls.py +++ b/dynamicweb/urls.py @@ -15,7 +15,7 @@ from webhook import views as webhook_views from membership import urls as membership_urls from ungleich_page.views import LandingView from django.views.generic import RedirectView -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy import debug_toolbar urlpatterns = [ @@ -30,7 +30,7 @@ urlpatterns = [ name="django.hosting"), url(r'^nosystemd/', include('nosystemd.urls', namespace="nosystemd")), url(r'^taggit_autosuggest/', include('taggit_autosuggest.urls')), - url(r'^jsi18n/(?P\S+?)/$', i18n.javascript_catalog), + url(r'^jsi18n/(?P\S+?)/$', i18n.JavaScriptCatalog.as_view()), url(r'^product/(?P[\w-]+)/$', PaymentOrderView.as_view(), name='show_product'), @@ -51,7 +51,7 @@ def home_view(request, *args, **kwargs): urlpatterns += i18n_patterns( - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^datacenterlight/', include('datacenterlight.urls', namespace="datacenterlight")), url(r'^hosting/', RedirectView.as_view(url=reverse_lazy('hosting:login')), diff --git a/dynamicweb/urls_multi.py b/dynamicweb/urls_multi.py index ffe59db5..44fc57c5 100644 --- a/dynamicweb/urls_multi.py +++ b/dynamicweb/urls_multi.py @@ -6,7 +6,7 @@ from django.views import static as static_view from django.views.generic import RedirectView urlpatterns = i18n_patterns( - url(r'^admin/', include(admin.site.urls)), + url(r'^admin/', admin.site.urls), url(r'^cms/', include('cms.urls')), url(r'^$', RedirectView.as_view(url='/cms')), ) diff --git a/hosting/mixins.py b/hosting/mixins.py index 5fb83383..dfb010cf 100644 --- a/hosting/mixins.py +++ b/hosting/mixins.py @@ -1,6 +1,6 @@ from django.shortcuts import redirect from django.conf import settings -from django.core.urlresolvers import reverse +from django.urls import reverse from opennebula_api.serializers import VirtualMachineTemplateSerializer from opennebula_api.models import OpenNebulaManager diff --git a/hosting/test_views.py b/hosting/test_views.py index 324aa4fa..60f87a5b 100644 --- a/hosting/test_views.py +++ b/hosting/test_views.py @@ -1,7 +1,7 @@ from django.conf import settings from django.test import TestCase -from django.core.urlresolvers import reverse -from django.core.urlresolvers import resolve +from django.urls import reverse +from django.urls import resolve from django.contrib.auth.tokens import default_token_generator from django.utils.http import urlsafe_base64_encode from django.utils.encoding import force_bytes diff --git a/hosting/urls.py b/hosting/urls.py index e34d27d6..c7437460 100644 --- a/hosting/urls.py +++ b/hosting/urls.py @@ -14,6 +14,8 @@ from .views import ( InvoiceListView, InvoiceDetailView, CheckUserVM ) +app_name = 'hosting' + urlpatterns = [ url(r'index/?$', IndexView.as_view(), name='index'), url(r'django/?$', DjangoHostingView.as_view(), name='djangohosting'), @@ -69,8 +71,8 @@ urlpatterns = [ name='reset_password'), url(r'reset-password-confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', PasswordResetConfirmView.as_view(), name='reset_password_confirm'), - url(r'^logout/?$', auth_views.logout, - {'next_page': '/hosting/login?logged_out=true'}, name='logout'), + url(r'^logout/?$', auth_views.LogoutView.as_view( + next_page='/hosting/login?logged_out=true'), name='logout'), url(r'^validate/(?P.*)/$', SignupValidatedView.as_view(), name='validate') ] diff --git a/hosting/views.py b/hosting/views.py index c2470707..5f982157 100644 --- a/hosting/views.py +++ b/hosting/views.py @@ -13,7 +13,7 @@ from django.contrib.auth.tokens import default_token_generator from django.core.exceptions import ValidationError from django.core.files.base import ContentFile from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger -from django.core.urlresolvers import reverse_lazy, reverse +from django.urls import reverse_lazy, reverse from django.db.models import Q from django.http import ( Http404, HttpResponseRedirect, HttpResponse, JsonResponse diff --git a/membership/migrations/0001_initial.py b/membership/migrations/0001_initial.py index 1704515c..8202c46c 100644 --- a/membership/migrations/0001_initial.py +++ b/membership/migrations/0001_initial.py @@ -36,7 +36,7 @@ class Migration(migrations.Migration): ('expiry_date', models.CharField(validators=[django.core.validators.RegexValidator('\\d{2}\\/\\d{4}', 'Use this pattern(MM/YYYY).')], max_length=50)), ('ccv', models.CharField(validators=[django.core.validators.RegexValidator('\\d{3,4}', 'Wrong CCV number.')], max_length=4)), ('payment_type', models.CharField(max_length=5, default='N')), - ('user_id', models.ForeignKey(to=settings.AUTH_USER_MODEL)), + ('user_id', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)), ], ), ] diff --git a/membership/models.py b/membership/models.py index abf30467..600aae4d 100644 --- a/membership/models.py +++ b/membership/models.py @@ -8,7 +8,7 @@ from django.contrib.auth.hashers import make_password from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, \ PermissionsMixin from django.contrib.sites.models import Site -from django.core.urlresolvers import reverse +from django.urls import reverse from django.core.validators import RegexValidator from django.db import models, IntegrityError from django.utils.crypto import get_random_string @@ -113,7 +113,7 @@ def validate_name(value): class CustomUser(AbstractBaseUser, PermissionsMixin): VALIDATED_CHOICES = ((0, 'Not validated'), (1, 'Validated')) - site = models.ForeignKey(Site, default=1) + site = models.ForeignKey(Site, default=1, on_delete=models.CASCADE) name = models.CharField(max_length=50, validators=[validate_name]) email = models.EmailField(unique=True) username = models.CharField(max_length=60, unique=True, null=True) @@ -267,7 +267,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): class StripeCustomer(models.Model): - user = models.OneToOneField(CustomUser) + user = models.OneToOneField(CustomUser, on_delete=models.CASCADE) stripe_id = models.CharField(unique=True, max_length=100) def __str__(self): @@ -359,7 +359,7 @@ class DeletedUser(models.Model): class Calendar(models.Model): datebooked = models.DateField() - user = models.ForeignKey(CustomUser) + user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) def __init__(self, *args, **kwargs): if kwargs.get('datebooked'): diff --git a/membership/tests.py b/membership/tests.py index 4b36fe4c..04a3bfdf 100644 --- a/membership/tests.py +++ b/membership/tests.py @@ -1,7 +1,7 @@ # import re # from django.test import TestCase -# from django.core.urlresolvers import reverse +# from django.urls import reverse # from django.core import mail diff --git a/nosystemd/admin.py b/nosystemd/admin.py index b07cea8c..93a912fc 100644 --- a/nosystemd/admin.py +++ b/nosystemd/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from django.utils.html import format_html -from django.core.urlresolvers import reverse +from django.urls import reverse from .models import Donation diff --git a/nosystemd/models.py b/nosystemd/models.py index 76d8ca40..db7ba9ce 100644 --- a/nosystemd/models.py +++ b/nosystemd/models.py @@ -11,7 +11,7 @@ class DonatorStatus(models.Model): (ACTIVE, 'Active'), (CANCELED, 'Canceled') ) - user = models.OneToOneField(CustomUser) + user = models.OneToOneField(CustomUser, on_delete=models.CASCADE) status = models.CharField(choices=STATUS_CHOICES, max_length=10, default=ACTIVE) def __str__(self): @@ -29,9 +29,9 @@ class DonatorStatus(models.Model): class Donation(models.Model): donation = models.FloatField() - donator = models.ForeignKey(StripeCustomer) + donator = models.ForeignKey(StripeCustomer, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) - billing_address = models.ForeignKey(BillingAddress) + billing_address = models.ForeignKey(BillingAddress, on_delete=models.CASCADE) last4 = models.CharField(max_length=4) cc_brand = models.CharField(max_length=10) stripe_charge_id = models.CharField(max_length=100, null=True) diff --git a/nosystemd/urls.py b/nosystemd/urls.py index 7c3965f8..7a9395ec 100644 --- a/nosystemd/urls.py +++ b/nosystemd/urls.py @@ -5,12 +5,14 @@ from .views import LandingView, LoginView, SignupView, PasswordResetView,\ PasswordResetConfirmView, DonationView, DonationDetailView, ChangeDonatorStatusDetailView,\ DonatorStatusDetailView, DonationListView +app_name = 'nosystemd' + urlpatterns = [ url(r'^$', LandingView.as_view(), name='landing'), url(r'^login/?$', LoginView.as_view(), name='login'), url(r'^signup/?$', SignupView.as_view(), name='signup'), - url(r'^logout/?$', auth_views.logout, - {'next_page': '/nosystemd/login?logged_out=true'}, name='logout'), + url(r'^logout/?$', auth_views.LogoutView.as_view( + next_page='/nosystemd/login?logged_out=true'), name='logout'), url(r'reset-password/?$', PasswordResetView.as_view(), name='reset_password'), url(r'reset-password-confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', PasswordResetConfirmView.as_view(), name='reset_password_confirm'), diff --git a/nosystemd/views.py b/nosystemd/views.py index cca19e8d..7f6e0fc7 100644 --- a/nosystemd/views.py +++ b/nosystemd/views.py @@ -2,7 +2,7 @@ from django.views.generic import TemplateView, FormView, DetailView, UpdateView, ListView from django.http import HttpResponseRedirect from django.shortcuts import render -from django.core.urlresolvers import reverse_lazy, reverse +from django.urls import reverse_lazy, reverse from django.contrib.auth.mixins import LoginRequiredMixin from django.conf import settings from django.contrib import messages diff --git a/opennebula_api/urls.py b/opennebula_api/urls.py index 87976e2e..4fd002f5 100644 --- a/opennebula_api/urls.py +++ b/opennebula_api/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url, include from rest_framework.urlpatterns import format_suffix_patterns from .views import VmCreateView, VmDetailsView +app_name = 'opennebula_api' + urlpatterns = { url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), diff --git a/ungleich/migrations/0001_initial.py b/ungleich/migrations/0001_initial.py index 1672c3cc..96093191 100644 --- a/ungleich/migrations/0001_initial.py +++ b/ungleich/migrations/0001_initial.py @@ -16,8 +16,8 @@ class Migration(migrations.Migration): fields=[ ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), ('image_header', models.ImageField(upload_to='image_header')), - ('extended_object', models.OneToOneField(editable=False, to='cms.Page')), - ('public_extension', models.OneToOneField(editable=False, related_name='draft_extension', null=True, to='ungleich.UngleichPage')), + ('extended_object', models.OneToOneField(editable=False, to='cms.Page', on_delete=models.CASCADE)), + ('public_extension', models.OneToOneField(editable=False, related_name='draft_extension', null=True, to='ungleich.UngleichPage', on_delete=models.CASCADE)), ], options={ 'abstract': False, diff --git a/ungleich/migrations/0002_ungleichpage_image.py b/ungleich/migrations/0002_ungleichpage_image.py index 44a6be64..b816171b 100644 --- a/ungleich/migrations/0002_ungleichpage_image.py +++ b/ungleich/migrations/0002_ungleichpage_image.py @@ -15,7 +15,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='ungleichpage', name='image', - field=filer.fields.image.FilerImageField(null=True, related_name='ungleinch_page_image', to='filer.Image', blank=True), + field=filer.fields.image.FilerImageField(null=True, related_name='ungleinch_page_image', to='filer.Image', blank=True, on_delete=models.CASCADE), preserve_default=True, ), ] diff --git a/ungleich/models.py b/ungleich/models.py index 194fdfef..cb5001bd 100644 --- a/ungleich/models.py +++ b/ungleich/models.py @@ -1,3 +1,4 @@ +from django.db import models from cms.extensions import PageExtension from cms.extensions.extension_pool import extension_pool from filer.fields.image import FilerImageField @@ -9,7 +10,7 @@ from filer.fields.image import FilerImageField class UngleichPage(PageExtension): # image_header = models.ImageField(upload_to='image_header') image = FilerImageField(null=True, blank=True, - related_name="ungleinch_page_image") + related_name="ungleinch_page_image", on_delete=models.CASCADE) class Meta: app_label = 'ungleich' diff --git a/ungleich/test_views.py b/ungleich/test_views.py index 17130857..d766562d 100644 --- a/ungleich/test_views.py +++ b/ungleich/test_views.py @@ -1,7 +1,7 @@ from utils.tests import BaseTestCase from django.conf import settings -from django.core.urlresolvers import reverse -from django.core.urlresolvers import resolve +from django.urls import reverse +from django.urls import resolve from django.utils.translation import get_language from django.utils.translation import activate from djangocms_blog.settings import get_setting diff --git a/ungleich/urls.py b/ungleich/urls.py index e833f03d..0991b97d 100644 --- a/ungleich/urls.py +++ b/ungleich/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from . import views from .views import PostDetailViewUngleich +app_name = 'ungleich' + urlpatterns = [ url(r'^$', views.PostListViewUngleich.as_view(), name="post-list"), # url(r'^$',views.PostListView.as_view()), diff --git a/ungleich_page/test_views.py b/ungleich_page/test_views.py index d67708bd..0fb238cb 100644 --- a/ungleich_page/test_views.py +++ b/ungleich_page/test_views.py @@ -1,6 +1,6 @@ from django.test import TestCase -from django.core.urlresolvers import reverse -from django.core.urlresolvers import resolve +from django.urls import reverse +from django.urls import resolve class ContactViewTest(TestCase): diff --git a/ungleich_page/urls.py b/ungleich_page/urls.py index 607b7042..9f13f9b2 100644 --- a/ungleich_page/urls.py +++ b/ungleich_page/urls.py @@ -2,6 +2,8 @@ from django.conf.urls import url from .views import ContactView, LandingView from django.utils.translation import ugettext_lazy as _ +app_name = 'ungleich_page' + urlpatterns = [ url(r'^$', LandingView.as_view(), name='landing'), # url(r'^ungleich_page/?$', LandingView.as_view(), name='landing'), diff --git a/ungleich_page/views.py b/ungleich_page/views.py index 01d1138d..a7864675 100644 --- a/ungleich_page/views.py +++ b/ungleich_page/views.py @@ -2,7 +2,7 @@ from django.contrib import messages from django.views.generic.edit import FormView from django.utils.translation import ugettext_lazy as _ -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.views.generic import View from django.shortcuts import render diff --git a/utils/models.py b/utils/models.py index 9e643707..98882c52 100644 --- a/utils/models.py +++ b/utils/models.py @@ -40,7 +40,7 @@ class BillingAddress(BaseBillingAddress): class UserBillingAddress(BaseBillingAddress): - user = models.ForeignKey(CustomUser, related_name='billing_addresses') + user = models.ForeignKey(CustomUser, related_name='billing_addresses', on_delete=models.CASCADE) current = models.BooleanField(default=True) def __str__(self): diff --git a/utils/views.py b/utils/views.py index f30a349a..fc851c18 100644 --- a/utils/views.py +++ b/utils/views.py @@ -5,7 +5,7 @@ from django.contrib import messages from django.contrib.auth import authenticate, login from django.contrib.auth.tokens import default_token_generator from django.core.files.base import ContentFile -from django.core.urlresolvers import reverse_lazy +from django.urls import reverse_lazy from django.http import HttpResponseRedirect from django.shortcuts import render from django.utils.encoding import force_bytes From 569948b7bb4885471332db2fac381b1fa4cc35f8 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 10:56:34 +0300 Subject: [PATCH 38/76] Add license files and a note for the vendored packages --- VENDORED.md | 7 +++++++ cmsplugin_filer_file/LICENSE | 24 ++++++++++++++++++++++++ cmsplugin_filer_folder/LICENSE | 24 ++++++++++++++++++++++++ cmsplugin_filer_image/LICENSE | 24 ++++++++++++++++++++++++ cmsplugin_filer_link/LICENSE | 24 ++++++++++++++++++++++++ cmsplugin_filer_utils/LICENSE | 24 ++++++++++++++++++++++++ cmsplugin_filer_video/LICENSE | 24 ++++++++++++++++++++++++ 7 files changed, 151 insertions(+) create mode 100644 VENDORED.md create mode 100644 cmsplugin_filer_file/LICENSE create mode 100644 cmsplugin_filer_folder/LICENSE create mode 100644 cmsplugin_filer_image/LICENSE create mode 100644 cmsplugin_filer_link/LICENSE create mode 100644 cmsplugin_filer_utils/LICENSE create mode 100644 cmsplugin_filer_video/LICENSE diff --git a/VENDORED.md b/VENDORED.md new file mode 100644 index 00000000..c67d4f3b --- /dev/null +++ b/VENDORED.md @@ -0,0 +1,7 @@ +# Vendored packages + +Copied into the repo because upstream is abandoned (last release 2016) and does not +run on Django 2+. Only compatibility edits were made; tables and migrations are unchanged. + +- `cmsplugin_filer_*` (cmsplugin-filer 1.1.3, BSD, license in each folder) +- `stored_messages` (django-stored-messages, BSD, license in folder) diff --git a/cmsplugin_filer_file/LICENSE b/cmsplugin_filer_file/LICENSE new file mode 100644 index 00000000..65a0ed6b --- /dev/null +++ b/cmsplugin_filer_file/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Stefan Foulis and contributors +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 authors nor the + names of its 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 HOLDERS 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. \ No newline at end of file diff --git a/cmsplugin_filer_folder/LICENSE b/cmsplugin_filer_folder/LICENSE new file mode 100644 index 00000000..65a0ed6b --- /dev/null +++ b/cmsplugin_filer_folder/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Stefan Foulis and contributors +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 authors nor the + names of its 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 HOLDERS 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. \ No newline at end of file diff --git a/cmsplugin_filer_image/LICENSE b/cmsplugin_filer_image/LICENSE new file mode 100644 index 00000000..65a0ed6b --- /dev/null +++ b/cmsplugin_filer_image/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Stefan Foulis and contributors +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 authors nor the + names of its 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 HOLDERS 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. \ No newline at end of file diff --git a/cmsplugin_filer_link/LICENSE b/cmsplugin_filer_link/LICENSE new file mode 100644 index 00000000..65a0ed6b --- /dev/null +++ b/cmsplugin_filer_link/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Stefan Foulis and contributors +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 authors nor the + names of its 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 HOLDERS 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. \ No newline at end of file diff --git a/cmsplugin_filer_utils/LICENSE b/cmsplugin_filer_utils/LICENSE new file mode 100644 index 00000000..65a0ed6b --- /dev/null +++ b/cmsplugin_filer_utils/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Stefan Foulis and contributors +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 authors nor the + names of its 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 HOLDERS 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. \ No newline at end of file diff --git a/cmsplugin_filer_video/LICENSE b/cmsplugin_filer_video/LICENSE new file mode 100644 index 00000000..65a0ed6b --- /dev/null +++ b/cmsplugin_filer_video/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2012, Stefan Foulis and contributors +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 authors nor the + names of its 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 HOLDERS 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. \ No newline at end of file From 5376d1c61144d8ff8303342679839bdc4cb7486b Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 12:31:27 +0300 Subject: [PATCH 39/76] Pin dependencies for Django 3.2 --- requirements.txt | 198 +++++++++++++++++++++++++---------------------- 1 file changed, 106 insertions(+), 92 deletions(-) diff --git a/requirements.txt b/requirements.txt index 7bc874a2..1167107c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,3 @@ -Django==2.2.28 -Babel==2.8.0 -Django-Select2==7.4.2 -Pillow==7.2.0 -URLObject==2.4.3 -Unidecode==1.1.1 -YURL==1.0.0 aldryn-apphooks-config==0.6.0 aldryn-boilerplates==0.8.0 aldryn-categories==1.2.0 @@ -13,104 +6,125 @@ aldryn-newsblog==2.2.1 aldryn-people==2.2.0 aldryn-search==1.1.0 aldryn-translation-tools==0.3.0 +amqp==5.3.1 +asgiref==3.8.1 +async-timeout==5.0.1 +babel==2.10.3 backport-collections==0.1 -django-appconf==1.0.4 -django-appdata==0.3.0 -django-bootstrap3==14.0.0 -django-classy-tags==1.0.0 -django-cms==3.7.3 -django-compressor==2.4 -django-debug-toolbar==2.2 -python-dotenv==0.13.0 -django-extensions==3.0.1 -django-filer==1.7.1 -django-filter==2.3.0 -django-formtools==2.2 -django-guardian==2.3.0 -django-haystack==2.8.1 -django-meta==1.6.1 -django-model-utils==4.0.0 -django-mptt==0.11.0 -django-parler==2.0.1 +backports-zoneinfo==0.2.1 +billiard==3.6.4.0 +cdist==6.9.8 +celery==5.2.7 +certifi==2026.7.22 +charset-normalizer==2.1.1 +click==8.1.8 +click-didyoumean==0.3.1 +click-plugins==1.1.1.2 +click-repl==0.3.0 +coverage==6.4.1 +cssselect2==0.7.0 +deprecated==1.3.1 +django==3.2.25 +django-admin-honeypot==1.1.0 +django-appconf==1.0.5 +django-appdata==0.3.2 +django-bootstrap3==21.2 +django-celery-results==2.4.0 +django-classy-tags==3.0.1 +django-cms==3.10.1 +django-compressor==4.0 +django-debug-toolbar==3.5.0 +django-extensions==3.1.5 +django-filer==2.2.1 +django-filter==22.1 +django-formtools==2.3 +django-guardian==2.4.0 +django-haystack==3.2.1 +django-js-asset==2.2.0 +django-meta==2.0.0 +django-model-utils==4.2.0 +django-mptt==0.13.4 +django-multisite==1.9.0 +django-parler==2.3 django-phonenumber-field==1.3.0 -django-polymorphic==2.1.2 -django-reversion==3.0.7 -django-sekizai==1.1.0 -django-sortedm2m==3.0.0 -django-spurl==0.6.7 +django-polymorphic==3.1.0 +django-recaptcha==3.0.0 +django-reversion==5.0.1 +django-sekizai==3.0.1 +django-select2==7.10.0 +django-sortedm2m==3.1.1 +django-spurl==0.6.8 django-standard-form==1.1.1 -django-taggit==1.2.0 +django-taggit==3.0.0 django-taggit-autosuggest==0.3.8 django-taggit-templatetags==0.2.5 django-templatetag-sugar==1.0 -django-treebeard==4.3.1 -djangocms-admin-style==1.5.0 +django-treebeard==4.7.1 +djangocms-admin-style==3.2.0 djangocms-apphook-setup==0.4.1 -djangocms-blog==1.1.1 -djangocms-file==2.4.0 +djangocms-attributes-field==4.0.0 +djangocms-blog==1.2.3 +djangocms-file==3.0.0 djangocms-flash==0.3.0 -djangocms-googlemap==1.4.0 +djangocms-googlemap==2.0.0 djangocms-inherit==0.2.2 -djangocms-link==2.6.1 -djangocms-page-meta==0.10.0 -djangocms-picture==2.4.0 +djangocms-link==3.0.0 +djangocms-page-meta==1.0.1 +djangocms-picture==4.0.0 djangocms-teaser==0.2.0 -djangocms-text-ckeditor==3.9.1 -djangocms-video==2.3.0 -easy-thumbnails==2.7 +djangocms-text-ckeditor==5.1.1 +djangocms-video==3.0.0 +djangorestframework==3.13.1 +easy-thumbnails==2.8.1 +flake8==4.0.1 html5lib==1.1 -ldap3==2.7 -lxml==4.5.1 -model-mommy==2.0.0 -phonenumbers==8.12.6 -phonenumberslite==8.12.6 -psycopg2==2.8.5 -pycryptodome==3.9.8 -pylibmc==1.6.1 -python-dateutil==2.8.1 -python-slugify==4.0.1 -pytz==2020.1 -rcssmin==1.0.6 -requests==2.24.0 -rjsmin==1.1.0 -six==1.15.0 -sqlparse==0.3.1 -stripe==2.48.0 -wheel==0.34.2 -django-admin-honeypot==1.1.0 -coverage==5.1 -djangorestframework==3.11.0 -flake8==3.8.3 -python-memcached==1.59 -celery==4.4.6 -redis==3.5.3 -django-celery-results==1.2.1 -kombu==4.6.11 +idna==3.15 +kombu==5.5.4 +ldap3==2.9.1 +lxml==4.9.0 mccabe==0.6.1 -pycodestyle==2.6.0 -pyflakes==2.2.0 -billiard==3.6.3.0 -amqp==2.6.0 -vine==1.3.0 -cdist==6.6.0 -pyotp==2.3.0 -django-recaptcha==2.0.6 -certifi==2020.6.20 -chardet==3.0.4 -django-js-asset==1.2.2 -djangocms-attributes-field==1.2.0 -entrypoints==0.3 -idna==2.10 -importlib-metadata==1.7.0 +model-mommy==2.0.0 +packaging==26.2 +phonenumbers==8.12.50 +phonenumberslite==8.12.50 +pillow==9.1.1 pluggy==0.13.1 -pyasn1==0.4.8 -requests-file==1.5.1 +prompt-toolkit==3.0.52 +psycopg2==2.9.3 +pyasn1==0.6.4 +pycodestyle==2.8.0 +pycryptodome==3.15.0 +pyflakes==2.4.0 +pylibmc==1.6.1 +pyotp==2.6.0 +python-dateutil==2.8.2 +python-dotenv==0.20.0 +python-memcached==1.59 +python-slugify==6.1.2 +pytz==2022.1 +rcssmin==1.1.0 +redis==4.3.4 +reportlab==4.4.3 +requests==2.28.1 +requests-file==3.0.1 +rjsmin==1.2.0 +setuptools==75.3.4 +six==1.16.0 +sqlparse==0.4.2 +stripe==3.5.0 +svglib==1.5.1 text-unidecode==1.3 -tldextract==2.2.2 -urllib3==1.25.9 +tinycss2==1.4.0 +tldextract==2.2.3 +typing-extensions==4.13.2 +tzdata==2026.5 +unidecode==1.1.1 +urllib3==1.26.20 +urlobject==2.4.3 +vine==5.1.0 +wcwidth==0.8.5 webencodings==0.5.1 -zipp==3.1.0 +wheel==0.37.1 +wrapt==2.0.1 +yurl==1.0.0 git+https://github.com/ungleich/python-oca.git#egg=oca -# transitive dependencies, pinned for reproducible builds -django-multisite==1.9.0 -future==1.0.0 From 1dc1c2601d049f0c3de462f73594e6556702f3bc Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 12:31:27 +0300 Subject: [PATCH 40/76] Settings for Django 3.2: DEFAULT_AUTO_FIELD, multisite SITE_ID check, aldryn compat shim --- dynamicweb/settings/base.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dynamicweb/settings/base.py b/dynamicweb/settings/base.py index 9593b51d..339846ef 100644 --- a/dynamicweb/settings/base.py +++ b/dynamicweb/settings/base.py @@ -10,8 +10,14 @@ import os # dotenv import dotenv +import django.utils.encoding from django.utils.translation import ugettext_lazy as _ +# The unmaintained aldryn-* packages still import this Django 2 helper +# (removed in Django 3.0); on Python 3 it is a no-op. +if not hasattr(django.utils.encoding, 'python_2_unicode_compatible'): + django.utils.encoding.python_2_unicode_compatible = lambda cls: cls + logger = logging.getLogger(__name__) @@ -268,6 +274,7 @@ USE_I18N = True USE_L10N = True +DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' USE_TZ = True FORMAT_MODULE_PATH = [ @@ -520,6 +527,9 @@ META_INCLUDE_KEYWORDS = ["ungleich", "hosting", "switzerland", "Schweiz", "Swiss", "cdist"] META_USE_SITES = True +# django-multisite's SiteID is a lazy object, not an int +SILENCED_SYSTEM_CHECKS = ['sites.E101'] + PARLER_LANGUAGES = {SITE_ID: ({'code': 'en-us'}, {'code': 'de'},)} AUTH_USER_MODEL = 'membership.CustomUser' From 28546434dc963408914a01c468f9edd9814042a5 Mon Sep 17 00:00:00 2001 From: Sajeyks Date: Tue, 6 Oct 2026 12:31:27 +0300 Subject: [PATCH 41/76] Use the static template library and drop six in the mailer --- alplora/templates/alplora/index.html | 2 +- alplora/templates/alplora/login.html | 2 +- cms_templates/djangocms_blog/_header_post_detail.html | 2 +- cms_templates/home_digitalglarus.html | 2 +- cms_templates/letscowork.html | 2 +- cms_templates/one_column.html | 2 +- cms_templates/two_columns.html | 2 +- .../cmsplugin_filer_folder/plugins/folder/default.html | 2 +- datacenterlight/templates/datacenterlight/add_ssh_key.html | 2 +- datacenterlight/templates/datacenterlight/base_hosting.html | 2 +- .../templates/datacenterlight/includes/_calculator_form.html | 2 +- .../templates/datacenterlight/includes/_navbar.html | 2 +- datacenterlight/templates/datacenterlight/index.html | 2 +- .../templates/datacenterlight/landing_payment.html | 2 +- datacenterlight/templates/datacenterlight/order_detail.html | 2 +- .../templates/datacenterlight/whydatacenterlight.html | 2 +- digitalglarus/templates/about.html | 2 +- digitalglarus/templates/base_glarus.html | 2 +- digitalglarus/templates/digitalglarus/about.html | 2 +- digitalglarus/templates/digitalglarus/booking.html | 2 +- .../templates/digitalglarus/booking_orders_detail.html | 2 +- .../templates/digitalglarus/booking_orders_list.html | 2 +- digitalglarus/templates/digitalglarus/booking_payment.html | 2 +- digitalglarus/templates/digitalglarus/booking_payment2.html | 2 +- .../templates/digitalglarus/confirm_reset_password.html | 2 +- .../digitalglarus/credit_card_edit_confirmation.html | 2 +- digitalglarus/templates/digitalglarus/edit_credit_card.html | 2 +- .../templates/digitalglarus/emails/booking_cancellation.html | 2 +- .../templates/digitalglarus/emails/booking_cancellation.txt | 2 +- .../emails/booking_cancellation_notification.html | 2 +- .../emails/booking_cancellation_notification.txt | 2 +- .../templates/digitalglarus/emails/booking_order_email.html | 2 +- .../templates/digitalglarus/emails/booking_order_email.txt | 2 +- .../templates/digitalglarus/emails/membership_charge.html | 2 +- .../templates/digitalglarus/emails/membership_charge.txt | 2 +- .../digitalglarus/emails/membership_monthly_charge.html | 2 +- .../digitalglarus/emails/membership_monthly_charge.txt | 2 +- .../templates/digitalglarus/emails/password_reset_email.html | 2 +- .../templates/digitalglarus/emails/password_reset_email.txt | 2 +- digitalglarus/templates/digitalglarus/history.html | 2 +- digitalglarus/templates/digitalglarus/index.html | 2 +- digitalglarus/templates/digitalglarus/login.html | 2 +- digitalglarus/templates/digitalglarus/login_hello.html | 2 +- .../templates/digitalglarus/membership_activated.html | 2 +- .../templates/digitalglarus/membership_deactivated.html | 2 +- .../digitalglarus/membership_deactivated_success.html | 2 +- .../templates/digitalglarus/membership_orders_detail.html | 2 +- .../templates/digitalglarus/membership_orders_list.html | 2 +- .../templates/digitalglarus/membership_payment.html | 2 +- .../templates/digitalglarus/membership_pricing.html | 2 +- digitalglarus/templates/digitalglarus/old_index.html | 2 +- digitalglarus/templates/digitalglarus/reset_password.html | 2 +- digitalglarus/templates/digitalglarus/signup.html | 2 +- digitalglarus/templates/digitalglarus/supportus.html | 2 +- digitalglarus/templates/digitalglarus/terms.html | 4 ++-- .../templates/digitalglarus/user_billing_address.html | 2 +- digitalglarus/templates/index.html | 2 +- digitalglarus/templates/letscowork.html | 2 +- digitalglarus/templates/new_base_glarus.html | 2 +- digitalglarus/templates/new_base_glarus_c.html | 2 +- digitalglarus/templates/support.html | 2 +- digitalglarus/templates/supporters.html | 2 +- hosting/templates/hosting/base.html | 2 +- hosting/templates/hosting/base_short.html | 2 +- hosting/templates/hosting/bill_detail.html | 2 +- hosting/templates/hosting/bill_error.html | 2 +- hosting/templates/hosting/bills.html | 2 +- hosting/templates/hosting/calculator_form.html | 2 +- hosting/templates/hosting/choice_ssh_keys.html | 2 +- hosting/templates/hosting/confirm_reset_password.html | 2 +- hosting/templates/hosting/create_virtual_machine.html | 2 +- hosting/templates/hosting/dashboard.html | 2 +- hosting/templates/hosting/django.html | 2 +- hosting/templates/hosting/emails/vm_charged.html | 2 +- hosting/templates/hosting/emails/vm_charged.txt | 2 +- hosting/templates/hosting/hosting_pricing.html | 2 +- hosting/templates/hosting/includes/_header.html | 2 +- hosting/templates/hosting/includes/_navbar.html | 2 +- hosting/templates/hosting/includes/_navbar_user.html | 2 +- hosting/templates/hosting/includes/_our_infrastructure.html | 2 +- hosting/templates/hosting/includes/_pricing.html | 2 +- hosting/templates/hosting/includes/_your_infrastructure.html | 2 +- hosting/templates/hosting/invoice_detail.html | 2 +- hosting/templates/hosting/invoices.html | 2 +- hosting/templates/hosting/login.html | 2 +- hosting/templates/hosting/nodejs.html | 2 +- hosting/templates/hosting/notifications.html | 2 +- hosting/templates/hosting/order_detail.html | 2 +- hosting/templates/hosting/orders.html | 2 +- hosting/templates/hosting/payment.html | 2 +- hosting/templates/hosting/rails.html | 2 +- hosting/templates/hosting/resend_activation_link.html | 2 +- hosting/templates/hosting/reset_password.html | 2 +- hosting/templates/hosting/settings.html | 2 +- hosting/templates/hosting/signup.html | 2 +- hosting/templates/hosting/signup_validate.html | 2 +- hosting/templates/hosting/user_key.html | 2 +- hosting/templates/hosting/user_keys.html | 2 +- hosting/templates/hosting/virtual_machine_detail.html | 2 +- hosting/templates/hosting/virtual_machines.html | 2 +- membership/templates/login.html | 2 +- nosystemd/templates/nosystemd/confirm_reset_password.html | 2 +- nosystemd/templates/nosystemd/donation.html | 2 +- nosystemd/templates/nosystemd/donation_detail.html | 2 +- nosystemd/templates/nosystemd/donations.html | 2 +- nosystemd/templates/nosystemd/donator_status.html | 2 +- nosystemd/templates/nosystemd/emails/donation_charge.html | 2 +- nosystemd/templates/nosystemd/emails/donation_charge.txt | 2 +- nosystemd/templates/nosystemd/landing.html | 2 +- nosystemd/templates/nosystemd/login.html | 2 +- nosystemd/templates/nosystemd/reset_password.html | 2 +- nosystemd/templates/nosystemd/signup.html | 2 +- templates/cms/digitalglarus/about.html | 2 +- templates/cms/digitalglarus/base.html | 2 +- templates/cms/digitalglarus/index.html | 2 +- templates/cms/digitalglarus/letscowork.html | 2 +- templates/cms/ungleichch/_header_base.html | 2 +- templates/cms/ungleichch/_menu.html | 2 +- templates/cms/ungleichch/base.html | 2 +- ungleich/templates/cms/ungleichch/_header_base.html | 2 +- ungleich/templates/cms/ungleichch/_menu.html | 2 +- ungleich/templates/cms/ungleichch/base_ungleich.html | 2 +- ungleich_page/templates/ungleich_page/404.html | 2 +- ungleich_page/templates/ungleich_page/_header_base.html | 2 +- ungleich_page/templates/ungleich_page/_menu.html | 2 +- utils/mailer.py | 5 ++--- 126 files changed, 128 insertions(+), 129 deletions(-) diff --git a/alplora/templates/alplora/index.html b/alplora/templates/alplora/index.html index ae388459..5c76229a 100644 --- a/alplora/templates/alplora/index.html +++ b/alplora/templates/alplora/index.html @@ -1,4 +1,4 @@ -{% load staticfiles i18n%} +{% load static i18n%} diff --git a/alplora/templates/alplora/login.html b/alplora/templates/alplora/login.html index 2d8d65ec..7e378fdd 100644 --- a/alplora/templates/alplora/login.html +++ b/alplora/templates/alplora/login.html @@ -1,4 +1,4 @@ -{% load staticfiles i18n%} +{% load static i18n%} diff --git a/cms_templates/djangocms_blog/_header_post_detail.html b/cms_templates/djangocms_blog/_header_post_detail.html index fb49e5ca..3c2bfcb5 100644 --- a/cms_templates/djangocms_blog/_header_post_detail.html +++ b/cms_templates/djangocms_blog/_header_post_detail.html @@ -1,4 +1,4 @@ -{% load cms_tags staticfiles %} +{% load cms_tags static %} {% load i18n %} diff --git a/cms_templates/home_digitalglarus.html b/cms_templates/home_digitalglarus.html index 0d965dbe..be0aafe7 100644 --- a/cms_templates/home_digitalglarus.html +++ b/cms_templates/home_digitalglarus.html @@ -1,5 +1,5 @@ {% extends "base_glarus.html" %} -{% load staticfiles cms_tags %} +{% load static cms_tags %} {% block content %} diff --git a/cms_templates/letscowork.html b/cms_templates/letscowork.html index 76a5c0d4..8037ea07 100644 --- a/cms_templates/letscowork.html +++ b/cms_templates/letscowork.html @@ -1,5 +1,5 @@ {% extends "base_glarus.html" %} -{% load staticfiles cms_tags %} +{% load static cms_tags %} {% block title %}crowdfunding{% endblock %} {% block content %} diff --git a/cms_templates/one_column.html b/cms_templates/one_column.html index b6d6495a..c6b2af1f 100644 --- a/cms_templates/one_column.html +++ b/cms_templates/one_column.html @@ -1,5 +1,5 @@ {% extends "base_glarus.html" %} -{% load staticfiles cms_tags %} +{% load static cms_tags %} {% block title %}About{% endblock %} {% block content %} diff --git a/cms_templates/two_columns.html b/cms_templates/two_columns.html index 83f30927..44d27e2d 100644 --- a/cms_templates/two_columns.html +++ b/cms_templates/two_columns.html @@ -1,5 +1,5 @@ {% extends "base_glarus.html" %} -{% load staticfiles cms_tags %} +{% load static cms_tags %} {% block title %}About{% endblock %} {% block content %} diff --git a/cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html b/cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html index b43e97d8..3bb5199b 100644 --- a/cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html +++ b/cmsplugin_filer_folder/templates/cmsplugin_filer_folder/plugins/folder/default.html @@ -1,4 +1,4 @@ -{% load i18n thumbnail sekizai_tags staticfiles %} +{% load i18n thumbnail sekizai_tags static %} {% addtoblock "css" %}{% endaddtoblock "css" %} {% addtoblock "js" %}{% endaddtoblock "js" %} {% addtoblock "js" %} diff --git a/datacenterlight/templates/datacenterlight/add_ssh_key.html b/datacenterlight/templates/datacenterlight/add_ssh_key.html index 44048bad..0eb2ecfa 100644 --- a/datacenterlight/templates/datacenterlight/add_ssh_key.html +++ b/datacenterlight/templates/datacenterlight/add_ssh_key.html @@ -1,5 +1,5 @@ -{% load staticfiles bootstrap3 i18n custom_tags humanize %} +{% load static bootstrap3 i18n custom_tags humanize %} {% block content %} {% block userkey_form %} diff --git a/datacenterlight/templates/datacenterlight/base_hosting.html b/datacenterlight/templates/datacenterlight/base_hosting.html index e50c4975..000f9c45 100644 --- a/datacenterlight/templates/datacenterlight/base_hosting.html +++ b/datacenterlight/templates/datacenterlight/base_hosting.html @@ -1,4 +1,4 @@ -{% load staticfiles i18n cms_tags sekizai_tags %} +{% load static i18n cms_tags sekizai_tags %} {% get_current_language as LANGUAGE_CODE %} diff --git a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html index 2c2b51dd..1b1dad5c 100644 --- a/datacenterlight/templates/datacenterlight/includes/_calculator_form.html +++ b/datacenterlight/templates/datacenterlight/includes/_calculator_form.html @@ -1,4 +1,4 @@ -{% load staticfiles i18n%} +{% load static i18n%} {% if vm_pricing %}