diff --git a/example/example/settings.py b/example/example/settings.py index 9ca30c3..c7d100c 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -1,37 +1,36 @@ """ Django settings for example project. +Generated by 'django-admin startproject' using Django 3.1.4. + For more information on this file, see -https://docs.djangoproject.com/en/1.6/topics/settings/ +https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see -https://docs.djangoproject.com/en/1.6/ref/settings/ +https://docs.djangoproject.com/en/3.1/ref/settings/ """ -from django.conf import global_settings +from pathlib import Path -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) -import os -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ +# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'nq-ck(53l4ne1p$2w77t6hpt)rvg4_rj1t%%xzphea+bn@i2d$' +SECRET_KEY = '7grew3z@*nm8ww&^_sw8$mk(azehml26ifn$(&rw_+g2*@w^wl' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -TEMPLATE_DEBUG = True - ALLOWED_HOSTS = [] # Application definition -INSTALLED_APPS = ( +INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', @@ -39,46 +38,74 @@ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_fsm', + 'django_fsm_log', 'fsm_admin', 'fsm_example', -) +] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) +] ROOT_URLCONF = 'example.urls' +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + WSGI_APPLICATION = 'example.wsgi.application' # Database -# https://docs.djangoproject.com/en/1.6/ref/settings/#databases +# https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + 'NAME': BASE_DIR / 'db.sqlite3', } } -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -) -TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( - 'django.core.context_processors.request', -) +# Password validation +# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] # Internationalization -# https://docs.djangoproject.com/en/1.6/topics/i18n/ +# https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' @@ -92,42 +119,6 @@ # Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/1.6/howto/static-files/ +# https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/' - - -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' - }, - 'simple': { - 'format': '%(levelname)s %(message)s' - }, - }, - 'handlers': { - 'null': { - 'level': 'DEBUG', - 'class': 'logging.NullHandler', - }, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'simple' - }, - }, - 'loggers': { - 'django': { - 'handlers': ['null'], - 'propagate': True, - 'level': 'INFO', - }, - 'geodata.models': { - 'handlers': ['console', ], - 'level': 'INFO', - } - } -} diff --git a/example/example/urls.py b/example/example/urls.py index dc0789d..d20735c 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -1,12 +1,21 @@ -from django.conf.urls import patterns, include, url +"""example URL Configuration +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/3.1/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" from django.contrib import admin -admin.autodiscover() +from django.urls import path -urlpatterns = patterns('', - # Examples: - # url(r'^$', 'example.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), - - url(r'^admin/', include(admin.site.urls)), -) +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/example/example/wsgi.py b/example/example/wsgi.py index 2cc360a..fc4f807 100644 --- a/example/example/wsgi.py +++ b/example/example/wsgi.py @@ -4,11 +4,13 @@ It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see -https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ +https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/ """ import os -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') + application = get_wsgi_application() diff --git a/example/fsm_example/admin.py b/example/fsm_example/admin.py index 6c150c1..35423eb 100644 --- a/example/fsm_example/admin.py +++ b/example/fsm_example/admin.py @@ -1,7 +1,8 @@ from django.contrib import admin from fsm_admin.mixins import FSMTransitionMixin -from fsm_example.models import PublishableModel + +from .models import PublishableModel # Example use of FSMTransitionMixin (order is important!) @@ -19,4 +20,5 @@ class PublishableModelAdmin(FSMTransitionMixin, admin.ModelAdmin): 'state', ) + admin.site.register(PublishableModel, PublishableModelAdmin) diff --git a/example/fsm_example/models.py b/example/fsm_example/models.py index 3ce571d..16e3f87 100644 --- a/example/fsm_example/models.py +++ b/example/fsm_example/models.py @@ -5,14 +5,14 @@ class State(object): - ''' + """ Constants to represent the `state`s of the PublishableModel - ''' - DRAFT = 'draft' # Early stages of content editing - APPROVED = 'approved' # Ready to be published - PUBLISHED = 'published' # Visible on the website - EXPIRED = 'expired' # Period for which the model is set to display has passed - DELETED = 'deleted' # Soft delete state + """ + DRAFT = 'draft' # Early stages of content editing + APPROVED = 'approved' # Ready to be published + PUBLISHED = 'published' # Visible on the website + EXPIRED = 'expired' # Period for which the model is set to display has passed + DELETED = 'deleted' # Soft delete state CHOICES = ( (DRAFT, DRAFT), @@ -24,7 +24,6 @@ class State(object): class PublishableModel(models.Model): - name = models.CharField(max_length=42, blank=False) # One state to rule them all @@ -43,16 +42,17 @@ class Meta: verbose_name = 'Post' verbose_name_plural = 'Posts' - def __unicode__(self): + def __str__(self): return self.name ######################################################## # Transition Conditions # These must be defined prior to the actual transitions - # to be refrenced. + # to be referenced. def has_display_dates(self): return self.display_from and self.display_until + has_display_dates.hint = 'Display dates are required to expire a page.' def can_display(self): @@ -60,6 +60,7 @@ def can_display(self): The display dates must be valid for the current date ''' return self.check_displayable(timezone.now()) + can_display.hint = 'The display dates may need to be adjusted.' def is_expired(self): @@ -73,7 +74,7 @@ def check_displayable(self, date): if not self.has_display_dates(): return True - displayable = self.display_from < date and self.display_until > date + displayable = self.display_from < date < self.display_until # Expired Pages should transition to the expired state if not displayable and not self.is_expired: self.expire() # Calling the expire transition @@ -84,15 +85,15 @@ def check_displayable(self, date): # Workflow (state) Transitions @transition(field=state, source=[State.APPROVED, State.EXPIRED], - target=State.PUBLISHED, - conditions=[can_display]) + target=State.PUBLISHED, + conditions=[can_display]) def publish(self): ''' Publish the object. ''' @transition(field=state, source=State.PUBLISHED, target=State.EXPIRED, - conditions=[has_display_dates]) + conditions=[has_display_dates]) def expire(self): ''' Automatically called when a object is detected as being not diff --git a/example/manage.py b/example/manage.py index 2605e37..625a05a 100755 --- a/example/manage.py +++ b/example/manage.py @@ -1,10 +1,22 @@ #!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" import os import sys -if __name__ == "__main__": - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") - - from django.core.management import execute_from_command_line +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'example.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/fsm_admin/mixins.py b/fsm_admin/mixins.py index 15590f7..6dc8999 100644 --- a/fsm_admin/mixins.py +++ b/fsm_admin/mixins.py @@ -4,8 +4,16 @@ from django.conf import settings from django.contrib import messages -from django.utils.translation import ugettext as _ -from django.utils.encoding import force_text +try: + from django.utils.translation import ugettext as _ +except ImportError: + from django.utils.translation import gettext_lazy as _ + +try: + from django.utils.encoding import force_text +except ImportError: + from django.utils.encoding import force_str as force_text + from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.http import HttpResponseRedirect diff --git a/requirements.txt b/requirements.txt index 96f3823..07fe46b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ Django>=1.6 -django-fsm==2.0.1 +django-fsm>=2.0.1 \ No newline at end of file