diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index ec4e305..50ac1cd 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,2 @@ github: [fabiocaccamo] -patreon: fabiocaccamo -ko_fi: fabiocaccamo -liberapay: fabiocaccamo -issuehunt: fabiocaccamo custom: ['https://www.paypal.me/fabiocaccamo'] diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..c898e38 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '26 21 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript', 'python' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.travis.yml b/.travis.yml index 656e70c..9091ee4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,6 +72,14 @@ jobs: env: TOX_ENV=py36-dj30-sqlite - python: "3.6" env: TOX_ENV=py36-dj30-postgres + - python: "3.6" + env: TOX_ENV=py36-dj31-sqlite + - python: "3.6" + env: TOX_ENV=py36-dj31-postgres + - python: "3.6" + env: TOX_ENV=py36-dj32-sqlite + - python: "3.6" + env: TOX_ENV=py36-dj32-postgres - python: "3.6" env: TOX_ENV=py36-djmaster-sqlite - python: "3.6" @@ -92,6 +100,14 @@ jobs: env: TOX_ENV=py37-dj30-sqlite - python: "3.7" env: TOX_ENV=py37-dj30-postgres + - python: "3.7" + env: TOX_ENV=py37-dj31-sqlite + - python: "3.7" + env: TOX_ENV=py37-dj31-postgres + - python: "3.7" + env: TOX_ENV=py37-dj32-sqlite + - python: "3.7" + env: TOX_ENV=py37-dj32-postgres - python: "3.7" env: TOX_ENV=py37-djmaster-sqlite - python: "3.7" @@ -104,10 +120,38 @@ jobs: env: TOX_ENV=py38-dj30-sqlite - python: "3.8" env: TOX_ENV=py38-dj30-postgres + - python: "3.8" + env: TOX_ENV=py38-dj31-sqlite + - python: "3.8" + env: TOX_ENV=py38-dj31-postgres + - python: "3.8" + env: TOX_ENV=py38-dj32-sqlite + - python: "3.8" + env: TOX_ENV=py38-dj32-postgres - python: "3.8" env: TOX_ENV=py38-djmaster-sqlite - python: "3.8" env: TOX_ENV=py38-djmaster-postgres + - python: "3.9" + env: TOX_ENV=py39-dj22-sqlite + - python: "3.9" + env: TOX_ENV=py39-dj22-postgres + - python: "3.9" + env: TOX_ENV=py39-dj30-sqlite + - python: "3.9" + env: TOX_ENV=py39-dj30-postgres + - python: "3.9" + env: TOX_ENV=py39-dj31-sqlite + - python: "3.9" + env: TOX_ENV=py39-dj31-postgres + - python: "3.9" + env: TOX_ENV=py39-dj32-sqlite + - python: "3.9" + env: TOX_ENV=py39-dj32-postgres + - python: "3.9" + env: TOX_ENV=py39-djmaster-sqlite + - python: "3.9" + env: TOX_ENV=py39-djmaster-postgres allow_failures: - env: TOX_ENV=py36-djmaster-sqlite - env: TOX_ENV=py36-djmaster-postgres @@ -115,6 +159,8 @@ jobs: - env: TOX_ENV=py37-djmaster-postgres - env: TOX_ENV=py38-djmaster-sqlite - env: TOX_ENV=py38-djmaster-postgres + - env: TOX_ENV=py39-djmaster-sqlite + - env: TOX_ENV=py39-djmaster-postgres install: - pip install tox services: diff --git a/CHANGELOG.md b/CHANGELOG.md index 53482eb..b25ef0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,140 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.18.2](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.18.2) - 2021-10-25 +- Fixed migration error. + +## [0.18.1](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.18.1) - 2021-10-25 +- Removed wrong migration. + +## [0.18.0](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.18.0) - 2021-10-24 +- Added foldable apps support. #117 +- Removed `css` field from `Theme` model. + +## [0.17.3](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.17.3) - 2021-10-12 +- Fixed `FileExtensionValidator` `TypeError` on django < 1.11. + +## [0.17.2](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.17.2) - 2021-10-08 +- Fixed `FileExtensionValidator` `TypeError` on django < 1.11. + +## [0.17.1](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.17.1) - 2021-09-24 +- Fixed `TemplateDoesNotExist` error on `django==4.0.a1` removing checking condition for `colorfield` package. #134 +- Fixed favicon fetching incompatible with `django-storages` `S3`. #128 + +## [0.17.0](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.17.0) - 2021-09-16 +- Added `logo_max_width` and `logo_max_height`. #127 + +## [0.16.4](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.16.4) - 2021-09-04 +- Fixed `0020_module_selected_colors` migration for multiple dbs. #132 +- Fixed sticky pagination `width` and `border-bottom`. +- Fixed inlines vertical overlow. +- Improved header elements vertical alignment. + +## [0.16.3](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.16.3) - 2021-04-26 +- Added `compat` module. +- Added missing `0021_file_extension_validator` migration. #126 +- Formatted migrations. + +## [0.16.2](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.16.2) - 2021-04-23 +- Added `python 3.9` and `django 3.2` to CI. +- Added `FileExtensionValidator` to `logo` and `favicon` fields. #112 +- Fixed `models.W042` warning on `django 3.2`. +- Fixed header `min-height`. +- Fixed selects `min-width`. +- Fixed changelist search, actions and submit button horizontal margins. +- Fixed related widget wrapper margin/padding with normal select and in inlines. +- Fixed tabular inlines horizontal scroll. + +## [0.16.1](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.16.1) - 2021-04-07 +- Fixed style of "Delete" and "Save" buttons in the delete confirmation page. #123 +- Overridden dark-mode css variables introduced in `django 3.2`. #124 + +## [0.16.0](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.16.0) - 2021-03-30 +- Added customizable colors for selected apps and models in dashboard. #122 +- Added `responsive_rtl.css` stylesheet. #98 +- Updated `vazir-font` version to `27.2.2`. #98 + +## [0.15.6](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.15.6) - 2021-03-26 +- Fixed `show_change_link` related modal support. #120 +- Fixed inline changelink style. +- Made globally available `presentRelatedObjectModal` and `presentRelatedObjectModalOnClickOn` js functions. + +## [0.15.5](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.15.5) - 2021-03-02 +- Fixed sticky submit and pagination `z-index` issue with related modal. + +## [0.15.4](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.15.4) - 2021-03-01 +- Fixed sticky submit and pagination `z-index` issue with sticky `list_filter` and `django-json-widget`. + +## [0.15.3](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.15.3) - 2021-02-08 +- Fixed sticky submit and pagination width when `admin.site.enable_nav_sidebar = False`. #113 + +## [0.15.2](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.15.2) - 2021-02-03 +- Fixed body classes template rendering. +- Improved sticky submit and pagination backward compatibility. + +## [0.15.1](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.15.1) - 2021-02-03 +- Fixed and improved sticky form controls and pagination style. + +## [0.15.0](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.15.0) - 2021-02-03 +- Added sticky form controls and pagination options. #110 +- Added support to 4-digit language code in language chooser. #111 +- Added theme css variables for third-party libraries. +- Fixed app module section link hover color. + +## [0.14.2](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.14.2) - 2021-01-04 +- Fixed tabular inline scroll bar. #101 +- Fixed module header selected link color. #102 +- Fixed main content width when `admin.site.enable_nav_sidebar = False`. #105 + +## [0.14.1](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.14.1) - 2020-11-12 +- Fixed sticky list-filter floating. #100 + +## [0.14.0](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.14.0) - 2020-10-15 +- Added list filter sticky option (only for `django >= 3.1.2`). +- Enabled list filter dropdown by default. +- Fixed changelist searchbar style. + +## [0.13.7](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.7) - 2020-10-14 +- Improved responsive widgets style. +- Prevented body horizontal scroll. +- Fixed tabular inline horizontal scroll. +- Fixed changelist filter min-width. +- Fixed changelist and toolbar theme rounded corners. +- Fixed calendar and timelist buttons theme color. +- Fixed list filter select size. +- Fixed content max-width with `django >= 3.1`. + +## [0.13.6](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.6) - 2020-10-14 +- Added persian language. #98 +- Fixed logo max-width on small screens. +- Fixed content max-width when nav-sidebar is collapsed. +- Fixed changelist max-width on medium screens. + +## [0.13.5](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.5) - 2020-09-15 +- Fixed loaddata error with initial_data.json fixture. #97 +- Fixed tests warning (admin.W411). +- Fixed changelist thead links color. +- Fixed changelist filter links hover color. + +## [0.13.4](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.4) - 2020-09-04 +- Added conditional imports to avoid Django deprecation warnings. #92 +- Changed admin header content vertical align to top. + +## [0.13.3](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.3) - 2020-09-02 +- Added `django-json-widget` theming support. + +## [0.13.2](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.2) - 2020-08-21 +- Fixed related modal not closing on edit save and create with django 3.1 - #96 + +## [0.13.1](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.1) - 2020-08-18 +- Improved header and nav-sidebar style. +- Added `max-width` to logo. +- Added `requirements-dev.txt` + +## [0.13.0](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.13.0) - 2020-08-05 +- Improved nav-sidebar style (`django>=3.1` support). +- Improved header style. + ## [0.12.3](https://github.com/fabiocaccamo/django-admin-interface/releases/tag/0.12.3) - 2020-07-20 - Fixed unreadable text in autocomplete multi-selects. #83 diff --git a/LICENSE.txt b/LICENSE.txt index 08c3cb0..82edf15 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016 Fabio Caccamo - fabio.caccamo@gmail.com +Copyright (c) 2016 - present, Fabio Caccamo - fabio.caccamo@gmail.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 57e2c38..8fd55af 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![](https://img.shields.io/pypi/v/django-admin-interface.svg?color=blue&logo=pypi&logoColor=white)](https://pypi.org/project/django-admin-interface/) [![](https://pepy.tech/badge/django-admin-interface)](https://pepy.tech/project/django-admin-interface) [![](https://img.shields.io/github/stars/fabiocaccamo/django-admin-interface?logo=github)](https://github.com/fabiocaccamo/django-admin-interface/) +[![](https://badges.pufler.dev/visits/fabiocaccamo/django-admin-interface?label=visitors&color=blue)](https://badges.pufler.dev) [![](https://img.shields.io/pypi/l/django-admin-interface.svg?color=blue)](https://github.com/fabiocaccamo/django-admin-interface/blob/master/LICENSE.txt) [![](https://img.shields.io/travis/fabiocaccamo/django-admin-interface?logo=travis&label=build)](https://travis-ci.org/fabiocaccamo/django-admin-interface) @@ -21,13 +22,17 @@ django-admin-interface is a modern **responsive flat admin interface customizabl - Beautiful default **django-theme** - Themes management and customization *(you can **customize admin title, logo and colors**)* - Responsive -- List filter dropdown *(optional)* -- `NEW` **Related modal** *(instead of the old popup window, optional)* -- `NEW` **Environment name/marker** -- `NEW` **Language chooser** +- Related modal *(instead of the old popup window)* +- Environment name/marker +- Language chooser +- List filter dropdown +- `NEW` **Foldable apps** *(accordions in the navigation bar)* +- `NEW` **List filter sticky** +- `NEW` **Form controls sticky** *(pagination and save/delete buttons)* - Compatibility / Style optimizations for: - `django-ckeditor` - `django-dynamic-raw-id` + - `django-json-widget` - `django-modeltranslation` - `django-tabbed-admin` - `sorl-thumbnail` @@ -47,7 +52,9 @@ INSTALLED_APPS = ( #... ) -X_FRAME_OPTIONS='SAMEORIGIN' # only if django version >= 3.0 +# only if django version >= 3.0 +X_FRAME_OPTIONS = 'SAMEORIGIN' +SILENCED_SYSTEM_CHECKS = ['security.W019'] ``` - Run ``python manage.py migrate`` - Run ``python manage.py collectstatic`` @@ -75,7 +82,7 @@ Run ``python manage.py loaddata admin_interface_theme_foundation.json`` Run ``python manage.py loaddata admin_interface_theme_uswds.json`` ### Add more themes -You can add a theme you've created through the admin to this repository by [sending us a PR](http://makeapullrequest.com/). Here are the steps to follow to add : +You can add a theme you've created through the admin to this repository by [sending us a PR](http://makeapullrequest.com/). Here are the steps to follow to add: 1. Export your exact theme as fixture using the `dumpdata` admin command: ``python manage.py dumpdata admin_interface.Theme --indent 4 -o admin_interface_theme_{{name}}.json --pks=N`` @@ -86,6 +93,34 @@ You can add a theme you've created through the admin to this repository by [send 4. Edit the section above to document your theme. +### Add theme support to third-party libraries +You can add **theme support to existing third-party libraries** using the following **css variables**: + +- `--admin-interface-title-color` +- `--admin-interface-logo-color` +- `--admin-interface-env-color` +- `--admin-interface-header-background-color:` +- `--admin-interface-header-text-color` +- `--admin-interface-header-link-color` +- `--admin-interface-header-link_hover-color` +- `--admin-interface-module-background-color` +- `--admin-interface-module-background-selected-color` +- `--admin-interface-module-text-color` +- `--admin-interface-module-link-color` +- `--admin-interface-module-link-selected-color` +- `--admin-interface-module-link-hover-color` +- `--admin-interface-generic-link-color` +- `--admin-interface-generic-link-hover-color` +- `--admin-interface-save-button-background-color` +- `--admin-interface-save-button-background-hover-color` +- `--admin-interface-save-button-text-color` +- `--admin-interface-delete-button-background-color` +- `--admin-interface-delete-button-background-hover-color` +- `--admin-interface-delete-button-text-color` +- `--admin-interface-related-modal-background-color` +- `--admin-interface-related-modal-background-opacity` + + ## Screenshots ###### Admin login ![django-admin-interface_login](https://cloud.githubusercontent.com/assets/1035294/11240233/55c8d4ba-8df1-11e5-9568-00fdc987ede8.gif) @@ -104,7 +139,60 @@ You can add a theme you've created through the admin to this repository by [send You can use [django-apptemplates](https://github.com/bittner/django-apptemplates), then add `{% extends "admin_interface:admin/base_site.html" %}` to your `base_site.html` ---- +## Testing +```bash +# create python virtual environment +virtualenv testing_django_admin_interface + +# activate virtualenv +cd testing_django_admin_interface && . bin/activate + +# clone repo +git clone https://github.com/fabiocaccamo/django-admin-interface.git src && cd src + +# install dependencies +pip install -r requirements.txt + +# run tests +tox +# or +python setup.py test +# or +python -m django test --settings "tests.settings" +``` ## License Released under [MIT License](LICENSE.txt). + +--- + +## Supporting + +I would like to spend more time on this project, especially to improve it and adding new features. + +As everyone knows open-source projects takes up a lot of time that is unpaid. :money_with_wings: + +If you are using this package in commercial project(s), please consider the idea to become a sponsor or donating once: + +- [GitHub Sponsor](https://github.com/sponsors/fabiocaccamo) +- [PayPal](https://www.paypal.me/fabiocaccamo) +- BTC: bc1q2t0pv8z3udpyuvfnx5kskhqdad4dcvtfuzmvjw +- ETH: 0x8B55Fb7798b5A9F797A4455C00821B6e53daca74 + +## See also + +- [`django-colorfield`](https://github.com/fabiocaccamo/django-colorfield) - simple color field for models with a nice color-picker in the admin. 🎨 + +- [`django-extra-settings`](https://github.com/fabiocaccamo/django-extra-settings) - config and manage typed extra settings using just the django admin. ⚙️ + +- [`django-maintenance-mode`](https://github.com/fabiocaccamo/django-maintenance-mode) - shows a 503 error page when maintenance-mode is on. 🚧 🛠️ + +- [`django-redirects`](https://github.com/fabiocaccamo/django-redirects) - redirects with full control. ↪️ + +- [`django-treenode`](https://github.com/fabiocaccamo/django-treenode) - probably the best abstract model / admin for your tree based stuff. 🌳 + +- [`python-benedict`](https://github.com/fabiocaccamo/python-benedict) - dict subclass with keylist/keypath support, I/O shortcuts (base64, csv, json, pickle, plist, query-string, toml, xml, yaml) and many utilities. 📘 + +- [`python-codicefiscale`](https://github.com/fabiocaccamo/python-codicefiscale) - encode/decode Italian fiscal codes - codifica/decodifica del Codice Fiscale. 🇮🇹 💳 + +- [`python-fsutil`](https://github.com/fabiocaccamo/python-fsutil) - file-system utilities for lazy devs. 🧟‍♂️ diff --git a/admin_interface/admin.py b/admin_interface/admin.py index d48bcb1..e60811f 100644 --- a/admin_interface/admin.py +++ b/admin_interface/admin.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- +from admin_interface.compat import gettext_lazy as _ from admin_interface.models import Theme from django.contrib import admin -from django.utils.translation import gettext_lazy as _ class ThemeAdmin(admin.ModelAdmin): @@ -38,6 +38,8 @@ class ThemeAdmin(admin.ModelAdmin): 'classes': ('wide', ), 'fields': ( 'logo', + 'logo_max_width', + 'logo_max_height', 'logo_color', 'logo_visible', ) @@ -67,8 +69,10 @@ class ThemeAdmin(admin.ModelAdmin): 'classes': ('wide', ), 'fields': ( 'css_module_background_color', + 'css_module_background_selected_color', 'css_module_text_color', 'css_module_link_color', + 'css_module_link_selected_color', 'css_module_link_hover_color', 'css_module_rounded_corners', ) @@ -96,6 +100,12 @@ class ThemeAdmin(admin.ModelAdmin): 'css_delete_button_text_color', ) }), + (_('Navigation Bar'), { + 'classes': ('wide', ), + 'fields': ( + 'foldable_apps', + ) + }), (_('Related Modal'), { 'classes': ('wide', ), 'fields': ( @@ -106,9 +116,19 @@ class ThemeAdmin(admin.ModelAdmin): 'related_modal_close_button_visible', ) }), + (_('Form Controls'), { + 'classes': ('wide', ), + 'fields': ( + 'form_submit_sticky', + 'form_pagination_sticky', + ) + }), (_('List Filter'), { 'classes': ('wide', ), - 'fields': ('list_filter_dropdown', ) + 'fields': ( + 'list_filter_dropdown', + 'list_filter_sticky', + ) }), (_('Recent Actions'), { 'classes': ('wide', ), diff --git a/admin_interface/apps.py b/admin_interface/apps.py index bdfc5fc..d15cb76 100644 --- a/admin_interface/apps.py +++ b/admin_interface/apps.py @@ -1,14 +1,16 @@ # -*- coding: utf-8 -*- +from admin_interface.compat import gettext_lazy as _ + from django.apps import AppConfig from django.db.models.signals import post_migrate -from django.utils.translation import gettext_lazy as _ class AdminInterfaceConfig(AppConfig): name = 'admin_interface' verbose_name = _('Admin Interface') + default_auto_field = 'django.db.models.AutoField' def ready(self): diff --git a/admin_interface/compat.py b/admin_interface/compat.py new file mode 100644 index 0000000..f47359c --- /dev/null +++ b/admin_interface/compat.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +import django + +if django.VERSION < (2, 0): + from django.utils.encoding import force_text as force_str + from django.utils.translation import ugettext_lazy as gettext_lazy +else: + from django.utils.encoding import force_str + from django.utils.translation import gettext_lazy + +if django.VERSION >= (1, 11): + from django.core.validators import FileExtensionValidator +else: + def FileExtensionValidator(*args, **kwargs): + def noop(*args, **kwargs): + pass + return noop diff --git a/admin_interface/fixtures/admin_interface_theme_bootstrap.json b/admin_interface/fixtures/admin_interface_theme_bootstrap.json index f5b3f33..eb3cde2 100644 --- a/admin_interface/fixtures/admin_interface_theme_bootstrap.json +++ b/admin_interface/fixtures/admin_interface_theme_bootstrap.json @@ -27,7 +27,6 @@ "css_delete_button_background_color": "#D9534F", "css_delete_button_background_hover_color": "#C9302C", "css_delete_button_text_color": "#FFFFFF", - "css": "", "related_modal_active": true, "related_modal_background_color": "#503873", "related_modal_background_opacity": 0.2, diff --git a/admin_interface/fixtures/admin_interface_theme_django.json b/admin_interface/fixtures/admin_interface_theme_django.json index 78ee05c..bec46cf 100644 --- a/admin_interface/fixtures/admin_interface_theme_django.json +++ b/admin_interface/fixtures/admin_interface_theme_django.json @@ -27,7 +27,6 @@ "css_delete_button_background_color": "#BA2121", "css_delete_button_background_hover_color": "#A41515", "css_delete_button_text_color": "#FFFFFF", - "css": "", "related_modal_active": true, "related_modal_background_color": "#000000", "related_modal_background_opacity": 0.2, diff --git a/admin_interface/fixtures/admin_interface_theme_foundation.json b/admin_interface/fixtures/admin_interface_theme_foundation.json index 125c936..fd121a3 100644 --- a/admin_interface/fixtures/admin_interface_theme_foundation.json +++ b/admin_interface/fixtures/admin_interface_theme_foundation.json @@ -27,7 +27,6 @@ "css_delete_button_background_color": "#CC4B37", "css_delete_button_background_hover_color": "#BF4634", "css_delete_button_text_color": "#FFFFFF", - "css": "", "related_modal_active": true, "related_modal_background_color": "#000000", "related_modal_background_opacity": 0.2, diff --git a/admin_interface/fixtures/admin_interface_theme_uswds.json b/admin_interface/fixtures/admin_interface_theme_uswds.json index 70810fc..877da40 100644 --- a/admin_interface/fixtures/admin_interface_theme_uswds.json +++ b/admin_interface/fixtures/admin_interface_theme_uswds.json @@ -27,7 +27,6 @@ "css_delete_button_background_color": "#CD2026", "css_delete_button_background_hover_color": "#981B1E", "css_delete_button_text_color": "#FFFFFF", - "css": "", "related_modal_active": true, "related_modal_background_color": "#000000", "related_modal_background_opacity": 0.8, diff --git a/admin_interface/fixtures/initial_data.json b/admin_interface/fixtures/initial_data.json index 8f8ecc3..bec46cf 100644 --- a/admin_interface/fixtures/initial_data.json +++ b/admin_interface/fixtures/initial_data.json @@ -1 +1,38 @@ -[{"model": "admin_interface.theme", "pk": 1, "fields": {"name": "Django", "active": true, "title": "Django administration", "title_visible": true, "logo": "", "logo_visible": true, "css_header_background_color": "#0C4B33", "css_header_title_color": "#F5DD5D", "css_header_text_color": "#44B78B", "css_header_link_color": "#FFFFFF", "css_header_link_hover_color": "#C9F0DD", "css_module_background_color": "#44B78B", "css_module_text_color": "#FFFFFF", "css_module_link_color": "#FFFFFF", "css_module_link_hover_color": "#C9F0DD", "css_module_rounded_corners": true, "css_generic_link_color": "#0C3C26", "css_generic_link_hover_color": "#156641", "css_save_button_background_color": "#0C4B33", "css_save_button_background_hover_color": "#0C3C26", "css_save_button_text_color": "#FFFFFF", "css_delete_button_background_color": "#BA2121", "css_delete_button_background_hover_color": "#A41515", "css_delete_button_text_color": "#FFFFFF", "css": "", "related_modal_active": true, "related_modal_background_color": "#000000", "related_modal_background_opacity": 0.2, "related_modal_rounded_corners": true, "list_filter_dropdown": false}}] \ No newline at end of file +[ + { + "model": "admin_interface.theme", + "fields": { + "name": "Django", + "active": true, + "title": "Django administration", + "title_color": "#F5DD5D", + "title_visible": true, + "logo": "", + "logo_color": "#FFFFFF", + "logo_visible": true, + "css_header_background_color": "#0C4B33", + "css_header_text_color": "#44B78B", + "css_header_link_color": "#FFFFFF", + "css_header_link_hover_color": "#C9F0DD", + "css_module_background_color": "#44B78B", + "css_module_text_color": "#FFFFFF", + "css_module_link_color": "#FFFFFF", + "css_module_link_hover_color": "#C9F0DD", + "css_module_rounded_corners": true, + "css_generic_link_color": "#0C3C26", + "css_generic_link_hover_color": "#156641", + "css_save_button_background_color": "#0C4B33", + "css_save_button_background_hover_color": "#0C3C26", + "css_save_button_text_color": "#FFFFFF", + "css_delete_button_background_color": "#BA2121", + "css_delete_button_background_hover_color": "#A41515", + "css_delete_button_text_color": "#FFFFFF", + "related_modal_active": true, + "related_modal_background_color": "#000000", + "related_modal_background_opacity": 0.2, + "related_modal_rounded_corners": true, + "list_filter_dropdown": false, + "recent_actions_visible": true + } + } +] \ No newline at end of file diff --git a/admin_interface/locale/fa/LC_MESSAGES/django.mo b/admin_interface/locale/fa/LC_MESSAGES/django.mo new file mode 100644 index 0000000..eea320d Binary files /dev/null and b/admin_interface/locale/fa/LC_MESSAGES/django.mo differ diff --git a/admin_interface/locale/fa/LC_MESSAGES/django.po b/admin_interface/locale/fa/LC_MESSAGES/django.po new file mode 100644 index 0000000..4fdddfc --- /dev/null +++ b/admin_interface/locale/fa/LC_MESSAGES/django.po @@ -0,0 +1,205 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# Amir Ajorloo , 2020. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: django-admin-interface\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-12 19:23+0330\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: Amir Ajorloo \n" +"Language-Team: Farsi \n" +"Language: Farsi" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: admin_interface/admin.py:21 +msgid "Environment" +msgstr "محیط" + +#: admin_interface/admin.py:30 +msgid "Language chooser" +msgstr "انتخاب زبان" + +#: admin_interface/admin.py:37 +msgid "Logo" +msgstr "لوگو" + +#: admin_interface/admin.py:45 +msgid "Favicon" +msgstr "آیکون تب" + +#: admin_interface/admin.py:49 +msgid "Title" +msgstr "عنوان" + +#: admin_interface/admin.py:57 +msgid "Header" +msgstr "هدر" + +#: admin_interface/admin.py:66 +msgid "Breadcrumbs / Module headers" +msgstr "مسیر صفحه" + +#: admin_interface/admin.py:76 +msgid "Generic Links" +msgstr "لینک‌های عمومی" + +#: admin_interface/admin.py:83 +msgid "Save Buttons" +msgstr "دکمه‌های ذخیره" + +#: admin_interface/admin.py:91 +msgid "Delete Buttons" +msgstr "دکمه‌های حذف" + +#: admin_interface/admin.py:99 +msgid "Related Modal" +msgstr "مدل‌های مرتبط" + +#: admin_interface/admin.py:109 +msgid "List Filter" +msgstr "فیلتر‌های لیست" + +#: admin_interface/admin.py:113 +msgid "Recent Actions" +msgstr "فعالیت‌های اخیر" + +#: admin_interface/apps.py:11 +msgid "Admin Interface" +msgstr "ظاهر ادمین" + +#: admin_interface/models.py:72 admin_interface/models.py:116 +#: admin_interface/models.py:135 +msgid "name" +msgstr "نام" + +#: admin_interface/models.py:75 admin_interface/models.py:132 +#: admin_interface/models.py:253 +msgid "active" +msgstr "فعال" + +#: admin_interface/models.py:79 +#: admin_interface/templates/admin/base_site.html:6 +#: admin_interface/templates/admin/base_site.html:60 +msgid "Django administration" +msgstr "مدیریت جنگو" + +#: admin_interface/models.py:81 +msgid "title" +msgstr "عنوان" + +#: admin_interface/models.py:87 admin_interface/models.py:102 +#: admin_interface/models.py:122 +msgid "color" +msgstr "رنگ" + +#: admin_interface/models.py:90 admin_interface/models.py:105 +#: admin_interface/models.py:289 +msgid "visible" +msgstr "نمایان" + +#: admin_interface/models.py:95 +msgid "Leave blank to use the default Django logo" +msgstr "برای نمایش لوگوی پیش فرض، خالی بگذارید." + +#: admin_interface/models.py:96 +msgid "logo" +msgstr "لوگو" + +#: admin_interface/models.py:110 +msgid "(.ico|.png|.gif - 16x16|32x32 px)" +msgstr "(.ico|.png|.gif - 16x16|32x32 px)" + +#: admin_interface/models.py:111 +msgid "favicon" +msgstr "آیکون تب" + +#: admin_interface/models.py:120 +msgid "" +"(red: #E74C3C, orange: #E67E22, yellow: #F1C40F, green: #2ECC71, blue: " +"#3498DB)" +msgstr "" +"(قرمز: #E74C3C, نارنجی: #E67E22, زرد: #F1C40F, سبز: #2ECC71, آبی: " +"#3498DB)" + +#: admin_interface/models.py:125 +msgid "visible in header (marker and name)" +msgstr "نمایان در هدر" + +#: admin_interface/models.py:128 +msgid "visible in favicon (marker)" +msgstr "نمایان در آیکون تب" + +#: admin_interface/models.py:134 +msgid "code" +msgstr "کد" + +#: admin_interface/models.py:141 +msgid "display" +msgstr "نمایش" + +#: admin_interface/models.py:148 admin_interface/models.py:173 +#: admin_interface/models.py:214 admin_interface/models.py:233 +#: admin_interface/models.py:259 +msgid "background color" +msgstr "رنگ پس‌زمینه" + +#: admin_interface/models.py:154 admin_interface/models.py:179 +#: admin_interface/models.py:226 admin_interface/models.py:245 +#: admin_interface/models.py:249 +msgid "text color" +msgstr "رنگ متن" + +#: admin_interface/models.py:160 admin_interface/models.py:185 +#: admin_interface/models.py:201 +msgid "link color" +msgstr "رنگ لینک" + +#: admin_interface/models.py:166 admin_interface/models.py:191 +#: admin_interface/models.py:207 +msgid "link hover color" +msgstr "رنگ لینک در حالت هاور" + +#: admin_interface/models.py:194 admin_interface/models.py:279 +msgid "rounded corners" +msgstr "گوشه‌های خمیده" + +#: admin_interface/models.py:220 admin_interface/models.py:239 +msgid "background hover color" +msgstr "رنگ پس‌زمینه در حالت هاور" + +#: admin_interface/models.py:276 +msgid "background opacity" +msgstr "شفافیت بکگراند" + +#: admin_interface/models.py:282 +msgid "close button visible" +msgstr "دکمه بستن نمایان باشد" + +#: admin_interface/models.py:286 +msgid "use dropdown" +msgstr "استفاده از دراپ داون" + +#: admin_interface/models.py:298 +msgid "Theme" +msgstr "تم" + +#: admin_interface/models.py:299 +msgid "Themes" +msgstr "تم‌ها" + +#: admin_interface/templates/admin/filter.html:13 +#: admin_interface/templates/admin_interface/dropdown_filter.html:4 +#, python-format +msgid " By %(filter_title)s " +msgstr " توسط %(filter_title)s " + +#: admin_interface/templates/admin/popup_response.html:3 +msgid "Popup closing..." +msgstr "پاپ آپ در حال بسته شدن..." diff --git a/admin_interface/migrations/0009_add_enviroment.py b/admin_interface/migrations/0009_add_enviroment.py index 86f0ac4..f5d35a5 100644 --- a/admin_interface/migrations/0009_add_enviroment.py +++ b/admin_interface/migrations/0009_add_enviroment.py @@ -15,11 +15,21 @@ class Migration(migrations.Migration): migrations.AddField( model_name='theme', name='env', - field=models.CharField(choices=[('development', 'Development'), ('testing', 'Testing'), ('staging', 'Staging'), ('production', 'Production')], default='development', max_length=50, verbose_name='enviroment'), + field=models.CharField( + choices=[ + ('development', 'Development'), + ('testing', 'Testing'), + ('staging', 'Staging'), + ('production', 'Production')], + default='development', + max_length=50, + verbose_name='enviroment'), ), migrations.AddField( model_name='theme', name='env_visible', - field=models.BooleanField(default=True, verbose_name='visible'), + field=models.BooleanField( + default=True, + verbose_name='visible'), ), ] diff --git a/admin_interface/migrations/0010_add_localization.py b/admin_interface/migrations/0010_add_localization.py index 6ec486d..12e3212 100644 --- a/admin_interface/migrations/0010_add_localization.py +++ b/admin_interface/migrations/0010_add_localization.py @@ -15,31 +15,55 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='theme', name='active', - field=models.BooleanField(default=True, verbose_name='active'), + field=models.BooleanField( + default=True, + verbose_name='active'), ), migrations.AlterField( model_name='theme', name='css', - field=models.TextField(blank=True, verbose_name='text color'), + field=models.TextField( + blank=True, + verbose_name='text color'), ), migrations.AlterField( model_name='theme', name='env', - field=models.CharField(choices=[('development', 'Development'), ('testing', 'Testing'), ('staging', 'Staging'), ('production', 'Production')], default='development', max_length=50, verbose_name='environment'), + field=models.CharField( + choices=[ + ('development', 'Development'), + ('testing', 'Testing'), + ('staging', 'Staging'), + ('production', 'Production') + ], + default='development', + max_length=50, + verbose_name='environment'), ), migrations.AlterField( model_name='theme', name='logo', - field=models.FileField(blank=True, help_text='Leave blank to use the default Django logo', upload_to='admin-interface/logo/', verbose_name='logo'), + field=models.FileField( + blank=True, + help_text='Leave blank to use the default Django logo', + upload_to='admin-interface/logo/', + verbose_name='logo'), ), migrations.AlterField( model_name='theme', name='name', - field=models.CharField(default='Django', max_length=50, verbose_name='name'), + field=models.CharField( + default='Django', + max_length=50, + verbose_name='name'), ), migrations.AlterField( model_name='theme', name='title', - field=models.CharField(blank=True, default='Django administration', max_length=50, verbose_name='title'), + field=models.CharField( + blank=True, + default='Django administration', + max_length=50, + verbose_name='title'), ), ] diff --git a/admin_interface/migrations/0012_update_verbose_names.py b/admin_interface/migrations/0012_update_verbose_names.py index 1ceb038..4d1a7b9 100644 --- a/admin_interface/migrations/0012_update_verbose_names.py +++ b/admin_interface/migrations/0012_update_verbose_names.py @@ -17,11 +17,21 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='theme', name='logo_color', - field=colorfield.fields.ColorField(blank=True, default='#FFFFFF', help_text='#FFFFFF', max_length=10, verbose_name='color'), + field=colorfield.fields.ColorField( + blank=True, + default='#FFFFFF', + help_text='#FFFFFF', + max_length=10, + verbose_name='color'), ), migrations.AlterField( model_name='theme', name='title_color', - field=colorfield.fields.ColorField(blank=True, default='#F5DD5D', help_text='#F5DD5D', max_length=10, verbose_name='color'), + field=colorfield.fields.ColorField( + blank=True, + default='#F5DD5D', + help_text='#F5DD5D', + max_length=10, + verbose_name='color'), ), ] diff --git a/admin_interface/migrations/0013_add_related_modal_close_button.py b/admin_interface/migrations/0013_add_related_modal_close_button.py index 67cce8b..f8b1b0a 100644 --- a/admin_interface/migrations/0013_add_related_modal_close_button.py +++ b/admin_interface/migrations/0013_add_related_modal_close_button.py @@ -15,6 +15,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name='theme', name='related_modal_close_button_visible', - field=models.BooleanField(default=True, verbose_name='close button visible'), + field=models.BooleanField( + default=True, + verbose_name='close button visible'), ), ] diff --git a/admin_interface/migrations/0014_name_unique.py b/admin_interface/migrations/0014_name_unique.py index 850d5a5..213c2fb 100644 --- a/admin_interface/migrations/0014_name_unique.py +++ b/admin_interface/migrations/0014_name_unique.py @@ -15,6 +15,10 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='theme', name='name', - field=models.CharField(default='Django', max_length=50, unique=True, verbose_name='name'), + field=models.CharField( + default='Django', + max_length=50, + unique=True, + verbose_name='name'), ), ] diff --git a/admin_interface/migrations/0015_add_language_chooser_active.py b/admin_interface/migrations/0015_add_language_chooser_active.py index fba1e04..23546a2 100644 --- a/admin_interface/migrations/0015_add_language_chooser_active.py +++ b/admin_interface/migrations/0015_add_language_chooser_active.py @@ -15,6 +15,8 @@ class Migration(migrations.Migration): migrations.AddField( model_name='theme', name='language_chooser_active', - field=models.BooleanField(default=True, verbose_name='active'), + field=models.BooleanField( + default=True, + verbose_name='active'), ), ] diff --git a/admin_interface/migrations/0016_add_language_chooser_display.py b/admin_interface/migrations/0016_add_language_chooser_display.py index 0f1a2f4..d950108 100644 --- a/admin_interface/migrations/0016_add_language_chooser_display.py +++ b/admin_interface/migrations/0016_add_language_chooser_display.py @@ -15,6 +15,13 @@ class Migration(migrations.Migration): migrations.AddField( model_name='theme', name='language_chooser_display', - field=models.CharField(choices=[('code', 'code'), ('name', 'name')], default='code', max_length=10, verbose_name='display'), + field=models.CharField( + choices=[ + ('code', 'code'), + ('name', 'name') + ], + default='code', + max_length=10, + verbose_name='display'), ), ] diff --git a/admin_interface/migrations/0017_change_list_filter_dropdown.py b/admin_interface/migrations/0017_change_list_filter_dropdown.py new file mode 100644 index 0000000..82556d5 --- /dev/null +++ b/admin_interface/migrations/0017_change_list_filter_dropdown.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0016_add_language_chooser_display'), + ] + + operations = [ + migrations.AlterField( + model_name='theme', + name='list_filter_dropdown', + field=models.BooleanField( + default=True, + verbose_name='use dropdown'), + ), + ] diff --git a/admin_interface/migrations/0018_theme_list_filter_sticky.py b/admin_interface/migrations/0018_theme_list_filter_sticky.py new file mode 100644 index 0000000..7f3c0bb --- /dev/null +++ b/admin_interface/migrations/0018_theme_list_filter_sticky.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0017_change_list_filter_dropdown'), + ] + + operations = [ + migrations.AddField( + model_name='theme', + name='list_filter_sticky', + field=models.BooleanField( + default=True, + verbose_name='sticky position'), + ), + ] diff --git a/admin_interface/migrations/0019_add_form_sticky.py b/admin_interface/migrations/0019_add_form_sticky.py new file mode 100644 index 0000000..1453a44 --- /dev/null +++ b/admin_interface/migrations/0019_add_form_sticky.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0018_theme_list_filter_sticky'), + ] + + operations = [ + migrations.AddField( + model_name='theme', + name='form_pagination_sticky', + field=models.BooleanField( + default=False, + verbose_name='sticky pagination'), + ), + migrations.AddField( + model_name='theme', + name='form_submit_sticky', + field=models.BooleanField( + default=False, + verbose_name='sticky submit'), + ), + ] diff --git a/admin_interface/migrations/0020_module_selected_colors.py b/admin_interface/migrations/0020_module_selected_colors.py new file mode 100644 index 0000000..4fe8114 --- /dev/null +++ b/admin_interface/migrations/0020_module_selected_colors.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations +from django.db.models import F + +import colorfield.fields + + +def default_link_selected(apps, schema_editor): + Theme = apps.get_model("admin_interface", "Theme") + db_alias = schema_editor.connection.alias + Theme.objects.using(db_alias).update( + css_module_link_selected_color=F('css_module_link_color')) + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0019_add_form_sticky'), + ] + + operations = [ + migrations.AddField( + model_name='theme', + name='css_module_background_selected_color', + field=colorfield.fields.ColorField( + blank=True, + default='#FFFFCC', + help_text='#FFFFCC', + max_length=10, + verbose_name='background selected color'), + ), + migrations.AddField( + model_name='theme', + name='css_module_link_selected_color', + field=colorfield.fields.ColorField( + blank=True, + default='#FFFFFF', + help_text='#FFFFFF', + max_length=10, + verbose_name='link selected color'), + ), + migrations.RunPython(default_link_selected), + ] diff --git a/admin_interface/migrations/0021_file_extension_validator.py b/admin_interface/migrations/0021_file_extension_validator.py new file mode 100644 index 0000000..74a14e2 --- /dev/null +++ b/admin_interface/migrations/0021_file_extension_validator.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from admin_interface.compat import FileExtensionValidator + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0020_module_selected_colors'), + ] + + operations = [ + migrations.AlterField( + model_name='theme', + name='favicon', + field=models.FileField( + blank=True, + help_text='(.ico|.png|.gif - 16x16|32x32 px)', + upload_to='admin-interface/favicon/', + validators=[ + FileExtensionValidator(allowed_extensions=[ + 'gif', 'ico', 'jpg', 'jpeg', 'png', 'svg' + ]) + ], + verbose_name='favicon'), + ), + migrations.AlterField( + model_name='theme', + name='logo', + field=models.FileField( + blank=True, + help_text='Leave blank to use the default Django logo', + upload_to='admin-interface/logo/', + validators=[ + FileExtensionValidator(allowed_extensions=[ + 'gif', 'jpg', 'jpeg', 'png', 'svg' + ]) + ], + verbose_name='logo'), + ), + ] diff --git a/admin_interface/migrations/0022_add_logo_max_width_and_height.py b/admin_interface/migrations/0022_add_logo_max_width_and_height.py new file mode 100644 index 0000000..183b855 --- /dev/null +++ b/admin_interface/migrations/0022_add_logo_max_width_and_height.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0021_file_extension_validator'), + ] + + operations = [ + migrations.AddField( + model_name='theme', + name='logo_max_height', + field=models.PositiveSmallIntegerField(blank=True, default=100, verbose_name='max height'), + ), + migrations.AddField( + model_name='theme', + name='logo_max_width', + field=models.PositiveSmallIntegerField(blank=True, default=400, verbose_name='max width'), + ), + ] diff --git a/admin_interface/migrations/0023_theme_foldable_apps.py b/admin_interface/migrations/0023_theme_foldable_apps.py new file mode 100644 index 0000000..6a852ff --- /dev/null +++ b/admin_interface/migrations/0023_theme_foldable_apps.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0022_add_logo_max_width_and_height'), + ] + + operations = [ + migrations.AddField( + model_name='theme', + name='foldable_apps', + field=models.BooleanField(default=True, verbose_name='foldable apps'), + ), + ] diff --git a/admin_interface/migrations/0024_remove_theme_css.py b/admin_interface/migrations/0024_remove_theme_css.py new file mode 100644 index 0000000..f060803 --- /dev/null +++ b/admin_interface/migrations/0024_remove_theme_css.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin_interface', '0023_theme_foldable_apps'), + ] + + operations = [ + migrations.RemoveField( + model_name='theme', + name='css', + ), + ] diff --git a/admin_interface/models.py b/admin_interface/models.py index 5a1a1f5..a5bc60a 100644 --- a/admin_interface/models.py +++ b/admin_interface/models.py @@ -3,13 +3,12 @@ from __future__ import unicode_literals from admin_interface.cache import del_cached_active_theme +from admin_interface.compat import FileExtensionValidator, force_str, gettext_lazy as _ from colorfield.fields import ColorField from django.db import models from django.db.models.signals import post_delete, post_save, pre_save -from django.utils.encoding import force_str -from django.utils.translation import gettext_lazy as _ from six import python_2_unicode_compatible @@ -94,6 +93,8 @@ class Theme(models.Model): logo = models.FileField( upload_to='admin-interface/logo/', blank=True, + validators=[FileExtensionValidator( + allowed_extensions=['gif', 'jpg', 'jpeg', 'png', 'svg'])], help_text=_('Leave blank to use the default Django logo'), verbose_name=_('logo')) logo_color = ColorField( @@ -102,6 +103,14 @@ class Theme(models.Model): help_text='#FFFFFF', max_length=10, verbose_name=_('color')) + logo_max_width = models.PositiveSmallIntegerField( + blank=True, + default=400, + verbose_name=_('max width')) + logo_max_height = models.PositiveSmallIntegerField( + blank=True, + default=100, + verbose_name=_('max height')) logo_visible = models.BooleanField( default=True, verbose_name=_('visible')) @@ -109,6 +118,8 @@ class Theme(models.Model): favicon = models.FileField( upload_to='admin-interface/favicon/', blank=True, + validators=[FileExtensionValidator( + allowed_extensions=['gif', 'ico', 'jpg', 'jpeg', 'png', 'svg'])], help_text=_('(.ico|.png|.gif - 16x16|32x32 px)'), verbose_name=_('favicon')) @@ -173,6 +184,12 @@ class Theme(models.Model): help_text='#44B78B', max_length=10, verbose_name=_('background color')) + css_module_background_selected_color = ColorField( + blank=True, + default='#FFFFCC', + help_text='#FFFFCC', + max_length=10, + verbose_name=_('background selected color')) css_module_text_color = ColorField( blank=True, default='#FFFFFF', @@ -185,6 +202,12 @@ class Theme(models.Model): help_text='#FFFFFF', max_length=10, verbose_name=_('link color')) + css_module_link_selected_color = ColorField( + blank=True, + default='#FFFFFF', + help_text='#FFFFFF', + max_length=10, + verbose_name=_('link selected color')) css_module_link_hover_color = ColorField( blank=True, default='#C9F0DD', @@ -246,10 +269,6 @@ class Theme(models.Model): max_length=10, verbose_name=_('text color')) - css = models.TextField( - blank=True, - verbose_name=_('text color')) - related_modal_active = models.BooleanField( default=True, verbose_name=_('active')) @@ -284,12 +303,27 @@ class Theme(models.Model): verbose_name=_('close button visible')) list_filter_dropdown = models.BooleanField( - default=False, + default=True, verbose_name=_('use dropdown')) + list_filter_sticky = models.BooleanField( + default=True, + verbose_name=_('sticky position')) + + foldable_apps = models.BooleanField( + default=True, + verbose_name=_('foldable apps')) + recent_actions_visible = models.BooleanField( default=True, verbose_name=_('visible')) + form_submit_sticky = models.BooleanField( + default=False, + verbose_name=_('sticky submit')) + form_pagination_sticky = models.BooleanField( + default=False, + verbose_name=_('sticky pagination')) + def set_active(self): self.active = True self.save() diff --git a/admin_interface/settings.py b/admin_interface/settings.py index f14e90c..1b4de03 100644 --- a/admin_interface/settings.py +++ b/admin_interface/settings.py @@ -23,6 +23,5 @@ def check_installed_app(app, app_dj_version_limit): def check_installed_apps(): - check_installed_app('colorfield', (4, 0)) check_installed_app('flat', (1, 9)) check_installed_app('flat_responsive', (2, 0)) diff --git a/admin_interface/static/admin/js/popup_response.js b/admin_interface/static/admin/js/popup_response.js index fb5e192..6dab531 100644 --- a/admin_interface/static/admin/js/popup_response.js +++ b/admin_interface/static/admin/js/popup_response.js @@ -10,8 +10,12 @@ openerRef = windowRef.parent; windowName = windowRef.name; widgetName = windowName.replace(/^(change|add|delete|lookup)_/, ''); + if (typeof(openerRef.id_to_windowname) === 'function') { + // django < 3.1 compatibility + widgetName = openerRef.id_to_windowname(widgetName); + } windowRef = { - name: openerRef.id_to_windowname(widgetName), + name: widgetName, close: function() { openerRef.dismissRelatedObjectModal(); } diff --git a/admin_interface/static/admin_interface/foldable-apps/foldable-apps.css b/admin_interface/static/admin_interface/foldable-apps/foldable-apps.css new file mode 100644 index 0000000..59a3744 --- /dev/null +++ b/admin_interface/static/admin_interface/foldable-apps/foldable-apps.css @@ -0,0 +1,70 @@ +.admin-interface.foldable-apps [class^="app-"].module { + visibility: hidden; +} + +.admin-interface.foldable-apps [class^="app-"].module.foldable-apps-ready { + visibility: visible; +} + +.admin-interface.foldable-apps [class^="app-"].module > table > caption { + position: relative; + z-index: 0; + /* pointer-events: none; */ + cursor: pointer; + -webkit-touch-callout: none; /* iOS Safari */ + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently supported by Chrome and Opera */ +} + +.admin-interface.foldable-apps [class^="app-"].module > table > caption > a { + display: inline-block; + /* pointer-events: all !important; */ + margin-right: 30px; +} + +.admin-interface.foldable-apps [class^="app-"].module > table > caption::after { + position: absolute; + top: 0; + right: 0; + z-index: 10; + width: auto; + height: 100%; + content: "−"; + font-size: 16px; + font-weight: lighter; + text-align: center; + padding-left: 10px; + padding-right: 10px; + cursor: pointer; + pointer-events: all !important; + display: flex; + flex-direction: column; + justify-content: center; +} + +@media (min-width: 1024px) { + .admin-interface.foldable-apps [class^="app-"].module > table > caption::after { + padding-left: 15px; + padding-right: 15px; + } +} + +.admin-interface.foldable-apps [class^="app-"].module > table { + display: table; + width: 100%; +} + +.admin-interface.foldable-apps [class^="app-"].module.collapsed { + margin-bottom: 10px; +} + +.admin-interface.foldable-apps [class^="app-"].module.collapsed > table > caption::after { + content: "+"; +} + +.admin-interface.foldable-apps [class^="app-"].module.collapsed > table > tbody { + display: none; +} diff --git a/admin_interface/static/admin_interface/foldable-apps/foldable-apps.js b/admin_interface/static/admin_interface/foldable-apps/foldable-apps.js new file mode 100644 index 0000000..28a7342 --- /dev/null +++ b/admin_interface/static/admin_interface/foldable-apps/foldable-apps.js @@ -0,0 +1,35 @@ +(function() { + window.onload = function() { + for (let moduleEl of document.querySelectorAll('.admin-interface.foldable-apps [class^="app-"].module')) { + // apply collapsed value from localstorage value + let moduleAppClass = null; + let moduleCollapsedClass = 'collapsed'; + moduleEl.classList.forEach(function(className) { + if (className.startsWith('app-')) { + moduleAppClass = className; + } + }); + if (moduleAppClass) { + let moduleAppKey = 'admin-interface.foldable-apps_' + moduleAppClass + '_collapsed'; + let moduleCollapsed = Boolean(parseInt((localStorage.getItem(moduleAppKey) || 0)) || 0); + if (moduleCollapsed === true) { + moduleEl.classList.add(moduleCollapsedClass); + } else { + moduleEl.classList.remove(moduleCollapsedClass); + } + // attach click for togggle collapsed class + for (let captionEl of moduleEl.querySelectorAll('caption')) { + captionEl.onclick = function(event) { + // only when not clicking on the app name link + if (event.target.tagName.toLowerCase() === 'caption') { + moduleEl.classList.toggle(moduleCollapsedClass); + moduleCollapsed = moduleEl.classList.contains(moduleCollapsedClass); + localStorage.setItem(moduleAppKey, (moduleCollapsed ? 1 : 0)); + } + }; + } + } + moduleEl.classList.add('foldable-apps-ready'); + } + }; +})(); \ No newline at end of file diff --git a/admin_interface/static/admin_interface/related-modal/related-modal.js b/admin_interface/static/admin_interface/related-modal/related-modal.js index e83044e..1c2ec84 100644 --- a/admin_interface/static/admin_interface/related-modal/related-modal.js +++ b/admin_interface/static/admin_interface/related-modal/related-modal.js @@ -130,6 +130,10 @@ if (typeof(django) !== 'undefined' && typeof(django.jQuery) !== 'undefined') el.click(data, presentRelatedObjectModal); } + // assign functions to global variables + window.presentRelatedObjectModal = presentRelatedObjectModal; + window.presentRelatedObjectModalOnClickOn = presentRelatedObjectModalOnClickOn; + // django 1.7 compatibility // $('a.add-another').removeAttr('onclick').click({ lookup:false }, presentRelatedObjectModal); presentRelatedObjectModalOnClickOn('a.add-another'); @@ -145,7 +149,10 @@ if (typeof(django) !== 'undefined' && typeof(django.jQuery) !== 'undefined') // django-dynamic-raw-id support - #61 // https://github.com/lincolnloop/django-dynamic-raw-id presentRelatedObjectModalOnClickOn('a.dynamic_raw_id-related-lookup', true); + + // show_change_link=True support + presentRelatedObjectModalOnClickOn('a.inlinechangelink'); }); })(django.jQuery); -} \ No newline at end of file +} diff --git a/admin_interface/templates/admin/base_site.html b/admin_interface/templates/admin/base_site.html index a0b20bd..8fd27a0 100644 --- a/admin_interface/templates/admin/base_site.html +++ b/admin_interface/templates/admin/base_site.html @@ -9,6 +9,7 @@ {% block extrastyle %} {% get_admin_interface_theme as theme %} {% get_admin_interface_version as version %} +{% get_current_language as current_lang %} -{% if theme.css %} - {{ theme.css|safe }} +{% if current_lang == 'fa' %} + {% endif %} - {% endblock %} {% block blockbots %} @@ -39,12 +43,25 @@ https://github.com/fabiocaccamo/django-admin-interface {% get_admin_interface_version as version %} {# https://github.com/elky/django-flat-responsive#important-note #} - + + {% include "admin_interface/favicon.html" %} +{% include "admin_interface/foldable-apps.html" %} {% include "admin_interface/related-modal.html" %} {% endblock %} -{% block bodyclass %}admin-interface flat-theme {% get_admin_interface_theme as theme %}{% if theme.name %}{{ theme.name|slugify }}-theme{% endif %}{% endblock %} +{% block extrahead %} +{{ block.super }} +{% endblock %} + +{% block bodyclass %} +{% get_admin_interface_theme as theme %} +flat-theme admin-interface +{% if theme.name %} {{ theme.name|slugify }}-theme {% endif %} +{% if theme.foldable_apps %} foldable-apps {% endif %} +{% if theme.form_submit_sticky %} sticky-submit {% endif %} +{% if theme.form_pagination_sticky %} sticky-pagination {% endif %} +{% endblock %} {% block branding %} {% get_admin_interface_theme as theme %} @@ -68,5 +85,5 @@ https://github.com/fabiocaccamo/django-admin-interface {% get_admin_interface_languages as languages %} {% include "admin_interface/language_chooser.html" %} {% endif %} -{% if theme.env_visible_in_header %} - {% endif %}{{ block.super }}
+{% if theme.env_visible_in_header %}
{% endif %}{{ block.super }}
{% endblock %} diff --git a/admin_interface/templates/admin_interface/css/admin-interface-fix.css b/admin_interface/templates/admin_interface/css/admin-interface-fix.css index db66099..cfc8ae8 100644 --- a/admin_interface/templates/admin_interface/css/admin-interface-fix.css +++ b/admin_interface/templates/admin_interface/css/admin-interface-fix.css @@ -1,79 +1,128 @@ +.admin-interface { + overflow-x: hidden; +} + +/* fix login */ .admin-interface.login #container { - width:320px; - margin:90px auto; - box-sizing:border-box; - -moz-box-sizing:border-box; - -webkit-box-sizing:border-box; + width: 100%; + max-width: 360px; + margin: 15px auto; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; } .admin-interface.login #content { - padding:15px 30px 10px 30px; + padding: 15px 30px 30px 30px; } -@media (max-width:767px){ - +@media (min-width:768px){ .admin-interface.login #container { - margin:0px auto; - } - - .admin-interface.login #content { - padding:20px 30px 30px 30px; + margin: 90px auto; } } .admin-interface.login #header { - padding:15px 30px 10px; - line-height:30px; + min-height: auto; + padding: 10px 30px; + line-height: 30px; + align-items: flex-start; + justify-content: flex-start; } -.admin-interface.login #branding h1 { +.admin-interface.login #header #branding h1 { margin-right:0; } -.admin-interface.login #branding h1 img.logo { - max-width:100%; - margin-right:0; +.admin-interface.login #header #branding h1 img.logo { + max-width: 100%; + margin-right: 0; } .admin-interface.login #header #branding h1 img.logo+span { - display:block; - margin-bottom: 5px; + display: block; +} + +.admin-interface.login #login-form { + display: flex; + flex-direction: column; } .admin-interface.login .submit-row { - padding-left:0; - text-align:right; + float: left; + width: 100%; + margin-top: 20px; + padding-top: 0; + padding-left: 0; + text-align: right; } -@media (max-width:767px){ - .admin-interface.login .submit-row label { - display:none; - } - .admin-interface.login .submit-row { - float: left; - width: 100%; - margin-top: 20px; - margin-bottom: 30px; - } +.admin-interface.login .submit-row label { + display: none; } +.admin-interface.login .submit-row input[type="submit"] { + width: 100%; + text-transform: uppercase; +} + +.admin-interface.login #footer { + display: none; +} +/* end login fix*/ + .admin-interface #header { - height:auto; - min-height:40px; + height: auto; + min-height: 55px; + box-sizing: border-box; + display: flex; + justify-content: space-between; + align-items: center; +} + +@media (max-width:1024px) { + .admin-interface #header { + align-items: center; + } } .admin-interface #branding h1 img.logo { - max-height:100px; margin-top:10px; margin-bottom:10px; margin-right:15px; display:inline-block !important; /* override inline display:none; */ } +@media (max-width:400px) { + .admin-interface #branding h1 img.logo { + max-width: 100%; + } +} + +.admin-interface #branding h1 span { + display: inline-block; +} + .admin-interface #branding h1 img.logo+span { white-space:nowrap; } +.admin-interface #user-tools { + margin-top: 10px; + margin-bottom: 10px; + white-space: nowrap; + align-self: flex-start; +} + +.admin-interface #user-tools br { + display: none; +} +@media (max-width: 768px) { + .admin-interface #user-tools br { + display: block; + } +} + .admin-interface fieldset.collapse { border: 1px solid transparent; } @@ -87,11 +136,12 @@ padding: 0 1px; } -/* -#changelist table tbody tr.selected { - background-color:#FFFFCC; +@media (min-width: 1024px) { + .admin-interface #changelist .actions .button, + .admin-interface #changelist .actions .action-counter { + margin-left: 8px; + } } -*/ .admin-interface #changelist .paginator { margin-top:-1px; /* merge 2 borders into 1 */ @@ -135,6 +185,12 @@ body.admin-interface .paginator a.showall:visited { margin-left: 5px; } +.admin-interface .inline-group .inlinechangelink { + background-size: contain; + padding-left: 15px; + margin-left: 10px; +} + .admin-interface .file-thumbnail > a { display: inline-block; } @@ -149,7 +205,6 @@ body.admin-interface .paginator a.showall:visited { .admin-interface form .form-row p.file-upload .clearable-file-input { display:inline-block; - margin-left: } .admin-interface form .form-row p.file-upload .clearable-file-input label { @@ -177,6 +232,7 @@ body.admin-interface .paginator a.showall:visited { .admin-interface form .form-row p.file-upload .clearable-file-input { display: block; margin-top: 10px; + margin-left: 0; margin-bottom: -10px; } @@ -217,13 +273,18 @@ body.admin-interface .paginator a.showall:visited { /* LIST FILTER */ .admin-interface .module.filtered h2 { - border-bottom-left-radius:0; - border-bottom-right-radius:0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; } .admin-interface .module.filtered #changelist-filter { - border-bottom-left-radius:4px; - border-bottom-right-radius:4px; + min-width: 240px; +} + +@media (max-width: 1024px) { + .admin-interface .module.filtered #changelist-filter { + min-width: 200px; + } } .admin-interface .module.filtered #changelist-filter h2 { @@ -240,10 +301,13 @@ body.admin-interface .paginator a.showall:visited { /* begin fix lateral padding to align text with field labels */ .admin-interface .module h2, .admin-interface.dashboard .module caption, +.admin-interface.dashboard .module th, .admin-interface .module.filtered h2, -.admin-interface .inline-group h2 { - padding-left:10px; - padding-right:10px; +.admin-interface .inline-group h2, +.admin-interface #nav-sidebar .module caption, +.admin-interface #nav-sidebar .module th { + padding-left: 10px; + padding-right: 10px; } /* end fix */ @@ -259,9 +323,23 @@ body.admin-interface .paginator a.showall:visited { } /* end fix */ -/* begin fix issue #10 - Related widget broken in long tabular inline */ -.admin-interface .inline-group .tabular .related-widget-wrapper { - white-space:nowrap; +/* begin fix tabular inlines horizontal scroll */ +.admin-interface .inline-related.tabular { + overflow-x: scroll; + overflow-y: hidden; +} +.admin-interface .inline-related.tabular fieldset.module { + display: contents; + width: 100%; + white-space: nowrap; + position: relative; +} +.admin-interface .inline-related.tabular fieldset.module h2 { + position: sticky; + left: 0; +} +.admin-interface .inline-related.tabular fieldset.module table tbody tr { + position: relative; } /* end fix */ @@ -306,17 +384,6 @@ body.admin-interface .paginator a.showall:visited { background-color:#AAAAAA; } -/* fix textarea horizontal scroll on Firefox */ -@media (max-width: 767px){ - .admin-interface .aligned .form-row textarea { - width: 100% !important; - flex: 0 1 auto; - } - .admin-interface .aligned .form-row input[type="file"] { - width: 100% !important; - } -} - /* improve responsive selector */ /* fix [stacked, not-stacked] equalize horizontal and vertical select padding for selector */ @@ -337,3 +404,186 @@ body.admin-interface .paginator a.showall:visited { .admin-interface .selector:not(.stacked) .selector-chosen select { height: calc(46px + 17.2em) !important; } + +/* fix nav-sidebar (added in django 3.1.0) */ +.admin-interface #toggle-nav-sidebar { + top: 10px; + left: 0; + z-index: 20; + flex: 0 0 30px; + width: 30px; + height: 45px; + margin-top: 10px; + margin-right: 10px; + background-color: #FFFFFF; + font-size: 16px; + border: 1px solid #eaeaea; + border-left: none; + outline: none; + -webkit-box-shadow: 4px 2px 8px -4px #DBDBDB; + -moz-box-shadow: 4px 2px 8px -4px #DBDBDB; + box-shadow: 4px 2px 8px -4px #DBDBDB; + /*transition: left .3s;*/ +} + +.admin-interface .toggle-nav-sidebar::before { + margin-top: -2px; +} + +.admin-interface .main > #nav-sidebar + .content, +.admin-interface .main.shifted > #nav-sidebar + .content { + max-width: 100%; +} + +/* hide nav-sidebar below 1280px to prevent horizontal overflow issues */ +@media (max-width:1279px) { + .admin-interface #nav-sidebar, + .admin-interface #toggle-nav-sidebar { + display: none; + } +} + +.admin-interface #nav-sidebar { + flex: 0 0 320px; + left: -320px; + margin-left: -360px; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + padding: 40px 40px 40px 0px; + border-top: none; + border-bottom: none; + border-left: none; + /*transition: left .3s, margin-left .3s;*/ +} + +@media (min-width:1280px) { + .admin-interface #main.shifted > #toggle-nav-sidebar { + left: 359px; + } + .admin-interface #main.shifted > #nav-sidebar { + left: 0px; + margin-left: 0; + } + .admin-interface #main:not(.shifted) > .content { + max-width: 100%; + } + .admin-interface.change-list:not(.popup) #main.shifted > #nav-sidebar + .content, + .admin-interface.change-form:not(.popup) #main.shifted > #nav-sidebar + .content { + max-width: calc(100% - 360px); + } +} + +/* fixed related widget and select2 */ +/* begin fix issue #10 - Related widget broken in long tabular inline */ +.admin-interface .related-widget-wrapper { + white-space: nowrap; +} +/* end fix */ + +.admin-interface .related-widget-wrapper select + .related-widget-wrapper-link, +.admin-interface .related-widget-wrapper .select2-container + .related-widget-wrapper-link { + margin-left: 12px !important; +} + +@media (min-width: 768px) { + .admin-interface.change-form select { + min-width: 150px; + } +} + +@media (min-width: 1024px) { + .admin-interface.change-form select { + min-width: 200px; + } +} + +.admin-interface.change-form .inline-related.tabular select { + min-width: auto !important; +} + +/* fixed time widget header border radius */ +.admin-interface .clockbox.module h2 { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} + +/* fix searchbar overriden padding */ +.admin-interface #changelist #changelist-search #searchbar { + padding: 2px 5px 3px 5px; +} + +@media (min-width: 1024px) { + .admin-interface #changelist #changelist-search #searchbar, + .admin-interface #changelist #changelist-search input[type="submit"], + .admin-interface #changelist #changelist-search .quiet { + margin-left: 8px; + } + .admin-interface #changelist #changelist-search label img { + vertical-align: text-top; + margin-right: 0px; + } +} + +@media (max-width: 1024px) { + .admin-interface #changelist #toolbar { + border-top: 1px solid #eee; + border-bottom: 1px solid #eee; + } + /* fixed changelist search size when there are search results and .quiet is visible */ + .admin-interface #changelist-search label img { + margin-top: 2px; + } + .admin-interface #changelist-search .quiet { + margin: 0 0 0 10px; + align-self: center; + flex-basis: content; + } +} + +@media (max-width: 767px) { + /* fixed responsive widgets */ + .admin-interface .aligned.collapsed .form-row { + display: none; + } + + .admin-interface .aligned .form-row > div { + display: flex; + max-width: 100vw; + flex-direction: column; + align-items: flex-start; + } + + .admin-interface .aligned .form-row .help { + margin-left: 0; + } + + .admin-interface .aligned .form-row .checkbox-row label { + margin: 10px 0 0 0; + padding: 0; + } + + .admin-interface .aligned .form-row input[type="file"], + .admin-interface .aligned .form-row input[type="text"], + .admin-interface .aligned .form-row input[type="email"] { + width: 100%; + } + + /* fix textarea horizontal scroll on Firefox */ + .admin-interface .aligned .form-row textarea { + width: 100% !important; + flex: 0 1 auto; + } + + .admin-interface .aligned .form-row .datetime input[type="text"] { + width: 50%; + } + + .admin-interface .aligned .form-row span + .file-upload { + margin-top: 10px; + } + + .admin-interface .aligned .form-row .file-upload input[type="file"] { + margin-top: 5px; + } +} \ No newline at end of file diff --git a/admin_interface/templates/admin_interface/css/admin-interface.css b/admin_interface/templates/admin_interface/css/admin-interface.css index ebe0f24..5521c12 100644 --- a/admin_interface/templates/admin_interface/css/admin-interface.css +++ b/admin_interface/templates/admin_interface/css/admin-interface.css @@ -1,8 +1,84 @@ +@media (prefers-color-scheme: dark) { + :root .admin-interface { + --primary: #79aec8; + --secondary: #417690; + --accent: #f5dd5d; + --primary-fg: #fff; + --body-fg: #333; + --body-bg: #fff; + --body-quiet-color: #666; + --body-loud-color: #000; + --header-color: #ffc; + --header-branding-color: var(--accent); + --header-bg: var(--secondary); + --header-link-color: var(--primary-fg); + --breadcrumbs-fg: #c4dce8; + --breadcrumbs-link-fg: var(--body-bg); + --breadcrumbs-bg: var(--primary); + --link-fg: #447e9b; + --link-hover-color: #036; + --link-selected-fg: #5b80b2; + --hairline-color: #e8e8e8; + --border-color: #ccc; + --error-fg: #ba2121; + --message-success-bg: #dfd; + --message-warning-bg: #ffc; + --message-error-bg: #ffefef; + --darkened-bg: #f8f8f8; + --selected-bg: #e4e4e4; + --selected-row: #ffc; + --button-fg: #fff; + --button-bg: var(--primary); + --button-hover-bg: #609ab6; + --default-button-bg: var(--secondary); + --default-button-hover-bg: #205067; + --close-button-bg: #888; + --close-button-hover-bg: #747474; + --delete-button-bg: #ba2121; + --delete-button-hover-bg: #a41515; + --object-tools-fg: var(--button-fg); + --object-tools-bg: var(--close-button-bg); + --object-tools-hover-bg: var(--close-button-hover-bg); + } +} + +:root .admin-interface { + --admin-interface-title-color: {{ theme.title_color }}; + --admin-interface-logo-color: {{ theme.logo_color }}; + --admin-interface-env-color: {{ theme.env_color }}; + --admin-interface-header-background-color: {{ theme.css_header_background_color }}; + --admin-interface-header-text-color: {{ theme.css_header_text_color }}; + --admin-interface-header-link-color: {{ theme.css_header_link_color }}; + --admin-interface-header-link_hover-color: {{ theme.css_header_link_hover_color }}; + --admin-interface-module-background-color: {{ theme.css_module_background_color }}; + --admin-interface-module-background-selected-color: {{ theme.css_module_background_selected_color }}; + --admin-interface-module-text-color: {{ theme.css_module_text_color }}; + --admin-interface-module-link-color: {{ theme.css_module_link_color }}; + --admin-interface-module-link-selected-color: {{ theme.css_module_link_selected_color }}; + --admin-interface-module-link-hover-color: {{ theme.css_module_link_hover_color }}; + --admin-interface-generic-link-color: {{ theme.css_generic_link_color }}; + --admin-interface-generic-link-hover-color: {{ theme.css_generic_link_hover_color }}; + --admin-interface-save-button-background-color: {{ theme.css_save_button_background_color }}; + --admin-interface-save-button-background-hover-color: {{ theme.css_save_button_background_hover_color }}; + --admin-interface-save-button-text-color: {{ theme.css_save_button_text_color }}; + --admin-interface-delete-button-background-color: {{ theme.css_delete_button_background_color }}; + --admin-interface-delete-button-background-hover-color: {{ theme.css_delete_button_background_hover_color }}; + --admin-interface-delete-button-text-color: {{ theme.css_delete_button_text_color }}; + --admin-interface-related-modal-background-color: {{ theme.related_modal_background_color }}; + --admin-interface-related-modal-background-opacity: {{ theme.related_modal_background_opacity }}; +} + .admin-interface #header { background: {{ theme.css_header_background_color }}; color: {{ theme.css_header_text_color }}; } +.admin-interface #header + #main { + {% if theme.css_header_background_color != theme.css_module_background_color %} + border-top: 10px solid {{ theme.css_module_background_color }}; + {% endif %} +} + .admin-interface .environment-label { } @@ -20,13 +96,33 @@ } .admin-interface .environment-label::after { - content: "{{ theme.env_name }}"; + content: "{{ theme.env_name }} - "; +} + +@media (max-width:1024px) { + .admin-interface .environment-label::after { + content: "{{ theme.env_name }}"; + } } .admin-interface .language-chooser { display: inline-block; - float: right; - margin-left: 20px; + position: absolute; + top: 15px; + right: 15px; + z-index: 10; +} +@media (min-width:768px) { + .admin-interface .language-chooser { + right: 30px; + } +} +@media (min-width:1024px) { + .admin-interface .language-chooser { + position: static; + float: right; + margin-left: 20px; + } } .admin-interface .language-chooser-hidden-form { @@ -49,13 +145,13 @@ background-image: url("data:image/svg+xml;utf8,"); } -.admin-interface #user-tools br { - display: none; -} -@media (max-width: 768px){ - .admin-interface #user-tools br { - display: block; - } +.admin-interface #branding h1 img.logo { + {% if theme.logo_max_width > 0 %} + max-width: {{ theme.logo_max_width }}px; + {% endif %} + {% if theme.logo_max_height > 0 %} + max-height: {{ theme.logo_max_height }}px; + {% endif %} } .admin-interface #header #user-tools a { @@ -68,6 +164,25 @@ border-bottom-color:rgba(255, 255, 255, 0.5); } +.admin-interface #nav-sidebar .current-app .section:link, +.admin-interface #nav-sidebar .current-app .section:visited { + color: {{ theme.css_module_link_selected_color }}; + font-weight: normal; +} + +.admin-interface #nav-sidebar .current-app .section:focus, +.admin-interface #nav-sidebar .current-app .section:hover { + color: {{ theme.css_module_link_hover_color }}; +} + +.admin-interface #nav-sidebar .current-model { + background: {{ theme.css_module_background_selected_color }}; +} + +.admin-interface #changelist table tbody tr.selected { + background-color: {{ theme.css_module_background_selected_color }}; +} + .admin-interface .module h2, .admin-interface .module caption, .admin-interface .module.filtered h2 { @@ -153,6 +268,14 @@ color:{{ theme.css_generic_link_hover_color }}; } +.admin-interface thead th a, +.admin-interface thead th a:link, +.admin-interface thead th a:visited, +.admin-interface thead th a:focus, +.admin-interface thead th a:hover { + color: #666666; +} + .admin-interface .button, .admin-interface input[type=submit], .admin-interface input[type=button], @@ -189,19 +312,22 @@ .admin-interface .button.default:hover, .admin-interface input[type=submit].default:active, .admin-interface input[type=submit].default:focus, -.admin-interface input[type=submit].default:hover { +.admin-interface input[type=submit].default:hover, +.admin-interface.delete-confirmation form .cancel-link:hover { background:{{ theme.css_save_button_background_hover_color }}; color:{{ theme.css_save_button_text_color }}; outline: none; } .admin-interface .submit-row a.deletelink:link, -.admin-interface .submit-row a.deletelink:visited { +.admin-interface .submit-row a.deletelink:visited, +.admin-interface.delete-confirmation form input[type="submit"] { background:{{ theme.css_delete_button_background_color }}; color:{{ theme.css_delete_button_text_color }}; } -.admin-interface .submit-row a.deletelink:hover { +.admin-interface .submit-row a.deletelink:hover, +.admin-interface.delete-confirmation form input[type="submit"]:hover { background:{{ theme.css_delete_button_background_hover_color }}; color:{{ theme.css_delete_button_text_color }}; } @@ -242,14 +368,57 @@ color:{{ theme.css_generic_link_hover_color }}; } +/* list-filter sticky */ +{% if theme.list_filter_sticky %} +@media (min-width: 768px) { + .admin-interface .module.filtered #changelist-filter { + position: sticky; + top: 40px; + float: right; + z-index: 30; + } + /* feature not available for django < 3.1.2 */ + .admin-interface .module.filtered #toolbar + #changelist-filter { + position: absolute; + top: 0px; + z-index: 30; + } +} +{% endif %} + +.admin-interface .module.filtered #changelist-filter { + {% if theme.css_module_rounded_corners %} + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + {% endif %} +} + +.admin-interface .module.filtered #changelist-filter #changelist-filter-clear a:focus, +.admin-interface .module.filtered #changelist-filter #changelist-filter-clear a:hover { + color: #666; + text-decoration: none; +} + +.admin-interface .module.filtered #changelist-filter li a:focus, +.admin-interface .module.filtered #changelist-filter li a:hover { + color: #666; + text-decoration: none; +} + .admin-interface .module.filtered #changelist-filter li.selected a, +.admin-interface .module.filtered #changelist-filter li.selected a:link, +.admin-interface .module.filtered #changelist-filter li.selected a:visited, +.admin-interface .module.filtered #changelist-filter li.selected a:focus, .admin-interface .module.filtered #changelist-filter li.selected a:hover { - color: {{ theme.css_generic_link_color }}; + /* color: #666; */ + color: {{ theme.css_generic_link_hover_color }}; } /* begin fix issue #11 - Inline border bottom should not be rounded */ .admin-interface .module h2, .admin-interface.dashboard .module caption, +.admin-interface #nav-sidebar .module th, +.admin-interface #nav-sidebar .module caption, .admin-interface .module.filtered h2 { {% if theme.css_module_rounded_corners %} border-radius:4px; @@ -274,3 +443,27 @@ background-color: {{ theme.css_module_background_color }}; color: {{ theme.css_module_text_color }}; } + +.admin-interface #toggle-nav-sidebar { + {% if theme.css_module_rounded_corners %} + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + {% endif %} + color: {{ theme.css_generic_link_color }}; +} + +.admin-interface #toggle-nav-sidebar:focus, +.admin-interface #toggle-nav-sidebar:hover, +.admin-interface #toggle-nav-sidebar:active { + color: {{ theme.css_generic_link_hover_color }}; +} + +.admin-interface .calendar td.selected a, +.admin-interface .calendar td a:active, +.admin-interface .calendar td a:focus, +.admin-interface .calendar td a:hover, +.admin-interface .timelist a:active, +.admin-interface .timelist a:focus, +.admin-interface .timelist a:hover { + background: {{ theme.css_module_background_color }}; +} diff --git a/admin_interface/templates/admin_interface/css/form-controls.css b/admin_interface/templates/admin_interface/css/form-controls.css new file mode 100644 index 0000000..500b52a --- /dev/null +++ b/admin_interface/templates/admin_interface/css/form-controls.css @@ -0,0 +1,95 @@ +/* sticky pagination */ + +.admin-interface.sticky-pagination.change-list #content-main { + padding-bottom: 4.375rem; +} + +.admin-interface.sticky-pagination.change-list .paginator { + width: 100%; + position: fixed; + bottom: 0; + right: 0; + z-index: 40; + box-sizing: border-box; + padding-left: 15px; + padding-right: 15px; + white-space: nowrap; + text-overflow: ellipsis; + border-radius: 0; + border-top: 1px solid #EEEEEE; + border-bottom: none; + margin: 0; +} + +.admin-interface.sticky-pagination.change-list.popup .paginator { + padding-left: 20px; + padding-right: 20px; +} + +@media (min-width:768px) { + .admin-interface.sticky-pagination.change-list:not(.popup) .paginator { + padding-left: 30px; + padding-right: 30px; + } +} + +@media (min-width:1024px) { + .admin-interface.sticky-pagination.change-list:not(.popup) .paginator { + padding-left: 40px; + padding-right: 40px; + } +} + +@media (min-width:1280px) { + .admin-interface.sticky-pagination.change-list:not(.popup) #main.shifted > #nav-sidebar + .content .paginator { + width: calc(100% - 360px); + } +} + +/* sticky submit */ + +@media (min-width:768px) { + .admin-interface.sticky-submit.change-form #content-main { + padding-bottom: 4.375rem; + } + + .admin-interface.sticky-submit.change-form .submit-row:last-of-type { + width: 100%; + position: fixed; + bottom: 0; + right: 0; + z-index: 40; + box-sizing: border-box; + padding-left: 15px; + padding-right: 15px; + white-space: nowrap; + text-overflow: ellipsis; + border-radius: 0; + border-top: 1px solid #EEEEEE; + border-bottom: none !important; + margin: 0; + } + + .admin-interface.sticky-submit.change-form.popup .submit-row:last-of-type { + padding-left: 20px; + padding-right: 20px; + } + + .admin-interface.sticky-submit.change-form:not(.popup) .submit-row:last-of-type { + padding-left: 30px; + padding-right: 30px; + } +} + +@media (min-width:1024px) { + .admin-interface.sticky-submit.change-form:not(.popup) .submit-row:last-of-type { + padding-left: 40px; + padding-right: 40px; + } +} + +@media (min-width:1280px) { + .admin-interface.sticky-submit.change-form:not(.popup) #main.shifted > #nav-sidebar + .content .submit-row:last-of-type { + width: calc(100% - 359px); + } +} \ No newline at end of file diff --git a/admin_interface/templates/admin_interface/css/json-widget.css b/admin_interface/templates/admin_interface/css/json-widget.css new file mode 100644 index 0000000..d51b9ff --- /dev/null +++ b/admin_interface/templates/admin_interface/css/json-widget.css @@ -0,0 +1,29 @@ +/* +django-json-widget support +https://github.com/jmrivas86/django-json-widget +*/ + +.admin-interface div.jsoneditor { + border: 1px solid {{ theme.css_module_background_color }}; + {% if theme.css_module_rounded_corners %} + border-radius: 4px; + overflow: hidden; + {% endif %} +} + +.admin-interface div.jsoneditor-menu { + background-color: {{ theme.css_module_background_color }}; + border-bottom: 1px solid {{ theme.css_module_background_color }}; +} + +.admin-interface div.jsoneditor-menu a.jsoneditor-poweredBy { + color: {{ theme.css_module_link_color }}; +} + +.admin-interface div.jsoneditor-contextmenu ul li button.jsoneditor-selected, +.admin-interface div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus, +.admin-interface div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover { + background-color: {{ theme.css_module_background_selected_color }}; + color: #000000; + font-weight: bold; +} diff --git a/admin_interface/templates/admin_interface/css/list-filter-dropdown.css b/admin_interface/templates/admin_interface/css/list-filter-dropdown.css index 396a207..d1fc14c 100644 --- a/admin_interface/templates/admin_interface/css/list-filter-dropdown.css +++ b/admin_interface/templates/admin_interface/css/list-filter-dropdown.css @@ -8,7 +8,7 @@ list-filter-dropdown } .admin-interface .list-filter-dropdown select { - background-color:#FFFFFF; - width:90%; - margin-right:5%; + background-color: #FFFFFF; + width: calc(100% - 30px); + margin-right: 15px; } \ No newline at end of file diff --git a/admin_interface/templates/admin_interface/css/rtl.css b/admin_interface/templates/admin_interface/css/rtl.css new file mode 100644 index 0000000..30c0b9d --- /dev/null +++ b/admin_interface/templates/admin_interface/css/rtl.css @@ -0,0 +1,34 @@ +[dir="rtl"] .admin-interface, +[dir="rtl"] .admin-interface * { + font-family: 'Vazir', sans-serif !important; +} + +[dir="rtl"] .admin-interface .main .toggle-nav-sidebar.sticky { + left: auto !important; + right: 0px !important; + margin-right: 0px !important; + margin-left: 10px; + border: 1px solid #eaeaea !important; + border-right: none !important; + border-top-right-radius: 0px !important; + border-bottom-right-radius: 0px !important; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + box-shadow: -4px 2px 8px -2px #DBDBDB !important; +} + +[dir="rtl"] .admin-interface #main.shifted > #toggle-nav-sidebar { + right: 359px !important; +} + +[dir="rtl"] .admin-interface #main > #nav-sidebar { + margin-right: -360px !important; + margin-left: 0px !important; + right: -320px !important; +} + +[dir="rtl"] .admin-interface #main.shifted > #nav-sidebar { + border-left: 1px solid #eaeaea; + margin-right: 0px !important; + padding: 40px 0px 40px 40px !important; +} diff --git a/admin_interface/templates/admin_interface/favicon.html b/admin_interface/templates/admin_interface/favicon.html index 7c7c5cb..6631c8e 100644 --- a/admin_interface/templates/admin_interface/favicon.html +++ b/admin_interface/templates/admin_interface/favicon.html @@ -1,7 +1,7 @@ {% load static %} {% if theme.favicon %} - + {% if theme.env_visible_in_favicon %} +{% endif %} diff --git a/admin_interface/templatetags/admin_interface_tags.py b/admin_interface/templatetags/admin_interface_tags.py index 13e0c0f..0b77b9e 100644 --- a/admin_interface/templatetags/admin_interface_tags.py +++ b/admin_interface/templatetags/admin_interface_tags.py @@ -42,7 +42,7 @@ def get_admin_interface_languages(context): if not request: return None full_path = request.get_full_path() - admin_nolang_url = re.sub(r'^\/([\w]{2})([\-\_]{1}[\w]{2})?\/', '/', full_path) + admin_nolang_url = re.sub(r'^\/([\w]{2})([\-\_]{1}[\w]{2,4})?\/', '/', full_path) if admin_nolang_url == full_path: # ImproperlyConfigured - must include admin urls using i18n_patterns: # from django.conf.urls.i18n import i18n_patterns diff --git a/admin_interface/version.py b/admin_interface/version.py index 2982891..9b6b449 100644 --- a/admin_interface/version.py +++ b/admin_interface/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -__version__ = '0.12.3' +__version__ = '0.18.2' diff --git a/requirements.txt b/requirements.txt index 9ad4e29..ade42a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,8 @@ +codecov +coverage +django>=1.7 django-colorfield django-flat-theme django-flat-responsive -six>=1.9.0 \ No newline at end of file +six>=1.9.0 +tox \ No newline at end of file diff --git a/setup.py b/setup.py index f3b0b21..7d2f91d 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages, setup -import os +import os, sys exec(open('admin_interface/version.py').read()) @@ -15,7 +15,8 @@ long_description_file_path = os.path.join(package_path, 'README.md') long_description_content_type = 'text/markdown' long_description = '' try: - with open(long_description_file_path) as f: + long_description_file_options = {} if sys.version_info[0] < 3 else { 'encoding':'utf-8' } + with open(long_description_file_path, 'r', **long_description_file_options) as f: long_description = f.read() except IOError: pass @@ -53,6 +54,8 @@ setup( 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', + 'Framework :: Django :: 3.1', + 'Framework :: Django :: 3.2', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', @@ -65,6 +68,7 @@ setup( 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: Software Development :: Build Tools', ], license='MIT', diff --git a/tests/settings.py b/tests/settings.py index 2008427..e4e6ad4 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + import django import os @@ -58,6 +60,7 @@ TEMPLATES = [{ 'context_processors': [ 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'django.template.context_processors.request', ] }, },] @@ -90,6 +93,15 @@ DATABASES = { 'default': database_config.get(database_engine), } +USE_I18N = True +LANGUAGES = ( + ('en', 'English', ), + ('it', 'Italian', ), +) +LANGUAGE_CODE = 'en' + +ROOT_URLCONF = 'tests.urls' + MEDIA_ROOT = os.path.join(BASE_DIR, 'admin_interface/public/media/') MEDIA_URL = '/media/' diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index 3a08aff..5c6233c 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + from django.core.management import call_command from django.test import TestCase @@ -17,6 +19,10 @@ class AdminInterfaceFixturesTestCase(TestCase): def __load_theme(self, theme_name): call_command('loaddata', 'admin_interface_theme_%s.json' % (theme_name, )) + def test_import_initial_data(self): + call_command('loaddata', 'initial_data.json') + self.assertEqual(Theme.objects.count(), 1); + def test_import_all(self): self.__load_theme('django') self.__load_theme('bootstrap') diff --git a/tests/test_models.py b/tests/test_models.py index fd3a79f..35b7155 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + from django.conf import settings from django.test import TestCase diff --git a/tests/test_settings.py b/tests/test_settings.py index 1396afc..3e92bd3 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals + import django from django.conf import settings from django.core.exceptions import ImproperlyConfigured diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index d54708a..f54aada 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- -from django.test import TestCase +from __future__ import unicode_literals + +from django.test import override_settings, TestCase from django.test.client import RequestFactory from django.template import Context, Template @@ -20,6 +22,63 @@ class AdminInterfaceTemplateTagsTestCase(TestCase): def __render_template(self, string, context=None): return Template(string).render(Context(context or {})) + def test_get_admin_interface_languages(self): + context = Context({ + 'request': self.request_factory.get('/en/admin/'), + }) + languages = templatetags.get_admin_interface_languages(context) + expected_languages = [ + {'code': 'en', 'name': 'English', 'default': True, 'active': True, 'activation_url': '/i18n/setlang/?next=/en/admin/'}, + {'code': 'it', 'name': 'Italian', 'default': False, 'active': False, 'activation_url': '/i18n/setlang/?next=/it/admin/'} + ] + self.assertEqual(len(languages), len(expected_languages)) + self.assertEqual(languages[0], expected_languages[0]) + self.assertEqual(languages[1], expected_languages[1]) + + @override_settings( + USE_I18N = False, + ) + def test_get_admin_interface_languages_with_i18n_disabled(self): + context = Context({ + 'request': self.request_factory.get('/en/admin/'), + }) + languages = templatetags.get_admin_interface_languages(context) + self.assertEqual(languages, None) + + @override_settings( + ROOT_URLCONF = 'tests.urls_without_i18n_patterns', + ) + def test_get_admin_interface_languages_without_i18n_url_patterns(self): + context = Context({ + 'request': self.request_factory.get('/en/admin/'), + }) + languages = templatetags.get_admin_interface_languages(context) + self.assertEqual(languages, None) + + @override_settings( + LANGUAGES = ( + ('en', 'English'), + ), + ) + def test_get_admin_interface_languages_without_multiple_languages(self): + context = Context({ + 'request': self.request_factory.get('/en/admin/'), + }) + languages = templatetags.get_admin_interface_languages(context) + self.assertEqual(languages, None) + + def test_get_admin_interface_languages_without_request(self): + context = Context({}) + languages = templatetags.get_admin_interface_languages(context) + self.assertEqual(languages, None) + + def test_get_admin_interface_languages_without_language_prefix_in_url(self): + context = Context({ + 'request': self.request_factory.get('/admin/'), + }) + languages = templatetags.get_admin_interface_languages(context) + self.assertEqual(languages, None) + def test_get_theme(self): Theme.objects.all().delete() context = Context({}) diff --git a/tests/urls.py b/tests/urls.py new file mode 100644 index 0000000..f4c3ad8 --- /dev/null +++ b/tests/urls.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import django +from django.contrib import admin +if django.VERSION < (2, 0): + from django.conf.urls import include, url as re_path +else: + from django.urls import include, re_path +from django.conf.urls.i18n import i18n_patterns + + +urlpatterns = [] +urlpatterns += [ + re_path(r'^i18n/', include('django.conf.urls.i18n')), +] +urlpatterns += i18n_patterns( + re_path(r'^admin/', admin.site.urls), +) diff --git a/tests/urls_without_i18n_patterns.py b/tests/urls_without_i18n_patterns.py new file mode 100644 index 0000000..2b10eac --- /dev/null +++ b/tests/urls_without_i18n_patterns.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from __future__ import unicode_literals + +import django +from django.contrib import admin +if django.VERSION < (2, 0): + from django.conf.urls import url as re_path +else: + from django.urls import re_path + + +urlpatterns = [ + re_path(r'^admin/', admin.site.urls), +] diff --git a/tox.ini b/tox.ini index 166c814..9bd4384 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,11 @@ [tox] envlist = py27-{dj17,dj18,dj19,dj110,dj111}-{sqlite,postgres}, - py34-{dj17,dj18,dj19,dj110,dj111,dj20}-{sqlite,postgres}, py35-{dj18,dj19,dj110,dj111,dj20,dj21,dj22}-{sqlite,postgres}, - py36-{dj18,dj19,dj110,dj111,dj20,dj21,dj22,dj30,djmaster}-{sqlite,postgres}, - py37-{dj20,dj21,dj22,dj30,djmaster}-{sqlite,postgres}, - py38-{dj22,dj30,djmaster}-{sqlite,postgres}, + py36-{dj18,dj19,dj110,dj111,dj20,dj21,dj22,dj30,dj31,dj32,djmaster}-{sqlite,postgres}, + py37-{dj20,dj21,dj22,dj30,dj31,dj32,djmaster}-{sqlite,postgres}, + py38-{dj22,dj30,dj31,dj32,djmaster}-{sqlite,postgres}, + py39-{dj22,dj30,dj31,dj32,djmaster}-{sqlite,postgres}, [testenv] passenv = CI TRAVIS TRAVIS_* deps = @@ -18,6 +18,8 @@ deps = dj21: Django == 2.1.* dj22: Django == 2.2.* dj30: Django == 3.0.* + dj31: Django == 3.1.* + dj32: Django == 3.2.* djmaster: https://github.com/django/django/archive/master.tar.gz # mysql: mysqlclient postgres: psycopg2-binary