pax_global_header00006660000000000000000000000064130154071360014512gustar00rootroot0000000000000052 comment=cbe894bbe12aeb0c468fc4cc987583772bcb3f30 django-fsm-admin-1.2.4/000077500000000000000000000000001301540713600146315ustar00rootroot00000000000000django-fsm-admin-1.2.4/.gitignore000066400000000000000000000001121301540713600166130ustar00rootroot00000000000000build dist *.egg-info docs/_build *.pyc .DS_Store db.sqlite3 *.tmp .eggs/ django-fsm-admin-1.2.4/MANIFEST.in000066400000000000000000000001511301540713600163640ustar00rootroot00000000000000include README.md include MIT-LICENSE.txt recursive-include fsm_admin * recursive-exclude fsm_admin *.pycdjango-fsm-admin-1.2.4/MIT-LICENSE.txt000066400000000000000000000020671301540713600171100ustar00rootroot00000000000000Copyright 2014 G Adventures http://www.gadventures.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 in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. django-fsm-admin-1.2.4/README.rst000066400000000000000000000057711301540713600163320ustar00rootroot00000000000000.. _QuickCast: http://quick.as/aq8fogo .. _django-fsm: https://github.com/kmmbvnr/django-fsm =============================== django-fsm-admin =============================== Mixin and template tags to integrate django-fsm_ state transitions into the django admin. Installation ------------ :: $ pip install django-fsm-admin Or from github: :: $ pip install -e git://github.com/gadventures/django-fsm-admin.git#egg=django-fsm-admin Usage ----- 1. Add ``fsm_admin`` to your INSTALLED_APPS 2. Ensure that you have "django.core.context_processors.request" in your TEMPLATE_CONTEXT_PROCESSORS in Django settings. If TEMPLATE_CONTEXT_PROCESSORS is not yet defined, add :: from django.conf import global_settings TEMPLATE_CONTEXT_PROCESSORS = global_settings.TEMPLATE_CONTEXT_PROCESSORS + ( 'django.core.context_processors.request', ) 3. In your ``admin.py`` file, use `FSMTransitionMixin` to add behaviour to your ModelAdmin. FSMTransitionMixin should be before `ModelAdmin`, the order is important. It assumes that your workflow state field is named `state` but you can override it or add additional workflow state fields with the attribute `fsm_field` :: from fsm_admin.mixins import FSMTransitionMixin class YourModelAdmin(FSMTransitionMixin, admin.ModelAdmin): # The name of one or more FSMFields on the model to transition fsm_field = ['wf_state',] admin.site.register(YourModel, YourModelAdmin) 4. By adding ``custom=dict(admin=False)`` to the transition decorator, one can disallow a transition to show up in the admin interface. This specially is useful, if the transition method accepts parameters without default values, since in **django-fsm-admin** no arguments can be passed into the transition method. :: @transition(field='state', source=['startstate'], target='finalstate', custom=dict(admin=False)) def do_something(self, some_param): # will not add a button "Do Something" to your admin model interface By adding ``FSM_ADMIN_FORCE_PERMIT = True`` to your configuration settings, the above restriction becomes the default. Then one must explicitly allow that a transition method shows up in the admin interface. :: @transition(field='state', source=['startstate'], target='finalstate', custom=dict(admin=True)) def proceed(self): # will add a button "Proceed" to your admin model interface This is useful, if most of your state transitions are handled by other means, such as external events communicating with the API of your application. Try the example --------------- :: $ git clone git@github.com:gadventures/django-fsm-admin.git $ cd django-fsm-admin $ mkvirtualenv fsm_admin $ pip install -r requirements.txt $ python setup.py develop $ cd example $ python manage.py syncdb $ python manage.py runserver Demo ---- Watch a QuickCast_ of the django-fsm-admin example .. image:: http://i.imgur.com/IJuE9Sr.png :width: 728px :height: 346px :target: QuickCast_ django-fsm-admin-1.2.4/example/000077500000000000000000000000001301540713600162645ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/example/000077500000000000000000000000001301540713600177175ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/example/__init__.py000066400000000000000000000000001301540713600220160ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/example/settings.py000066400000000000000000000061261301540713600221360ustar00rootroot00000000000000""" Django settings for example project. For more information on this file, see https://docs.djangoproject.com/en/1.6/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/1.6/ref/settings/ """ from django.conf import global_settings # Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os BASE_DIR = os.path.dirname(os.path.dirname(__file__)) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'nq-ck(53l4ne1p$2w77t6hpt)rvg4_rj1t%%xzphea+bn@i2d$' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True TEMPLATE_DEBUG = True ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = ( 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django_fsm', 'fsm_admin', 'fsm_example', ) MIDDLEWARE_CLASSES = ( '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' WSGI_APPLICATION = 'example.wsgi.application' # Database # https://docs.djangoproject.com/en/1.6/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(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', ) # Internationalization # https://docs.djangoproject.com/en/1.6/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.6/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', } } } django-fsm-admin-1.2.4/example/example/urls.py000066400000000000000000000004521301540713600212570ustar00rootroot00000000000000from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', # Examples: # url(r'^$', 'example.views.home', name='home'), # url(r'^blog/', include('blog.urls')), url(r'^admin/', include(admin.site.urls)), ) django-fsm-admin-1.2.4/example/example/wsgi.py000066400000000000000000000006051301540713600212430ustar00rootroot00000000000000""" WSGI config for example project. 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/ """ import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") from django.core.wsgi import get_wsgi_application application = get_wsgi_application() django-fsm-admin-1.2.4/example/fsm_example/000077500000000000000000000000001301540713600205645ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/fsm_example/__init__.py000066400000000000000000000000001301540713600226630ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/fsm_example/admin.py000066400000000000000000000010061301540713600222230ustar00rootroot00000000000000from django.contrib import admin from fsm_admin.mixins import FSMTransitionMixin from fsm_example.models import PublishableModel # Example use of FSMTransitionMixin (order is important!) class PublishableModelAdmin(FSMTransitionMixin, admin.ModelAdmin): list_display = ( 'name', 'display_from', 'display_until', 'state', ) list_filter = ( 'state', ) readonly_fields = ( 'state', ) admin.site.register(PublishableModel, PublishableModelAdmin) django-fsm-admin-1.2.4/example/fsm_example/models.py000066400000000000000000000066001301540713600224230ustar00rootroot00000000000000from django.db import models from django.utils import timezone from django_fsm import FSMField, transition 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 CHOICES = ( (DRAFT, DRAFT), (APPROVED, APPROVED), (PUBLISHED, PUBLISHED), (EXPIRED, EXPIRED), (DELETED, DELETED), ) class PublishableModel(models.Model): name = models.CharField(max_length=42, blank=False) # One state to rule them all state = FSMField( default=State.DRAFT, verbose_name='Publication State', choices=State.CHOICES, protected=True, ) # For scheduled publishing display_from = models.DateTimeField(blank=True, null=True) display_until = models.DateTimeField(blank=True, null=True) class Meta: verbose_name = 'Post' verbose_name_plural = 'Posts' def __unicode__(self): return self.name ######################################################## # Transition Conditions # These must be defined prior to the actual transitions # to be refrenced. 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): ''' 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): return self.state == State.EXPIRED def check_displayable(self, date): ''' Check that the current date falls within this object's display dates, if set, otherwise default to being displayable. ''' if not self.has_display_dates(): return True displayable = self.display_from < date and self.display_until > date # Expired Pages should transition to the expired state if not displayable and not self.is_expired: self.expire() # Calling the expire transition self.save() return displayable ######################################################## # Workflow (state) Transitions @transition(field=state, source=[State.APPROVED, State.EXPIRED], 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]) def expire(self): ''' Automatically called when a object is detected as being not displayable. See `check_displayable` ''' self.display_until = timezone.now() @transition(field=state, source=State.PUBLISHED, target=State.APPROVED) def unpublish(self): ''' Revert to the approved state ''' @transition(field=state, source=State.DRAFT, target=State.APPROVED) def approve(self): ''' After reviewed by stakeholders, the Page is approved. ''' django-fsm-admin-1.2.4/example/fsm_example/templates/000077500000000000000000000000001301540713600225625ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/fsm_example/templates/admin/000077500000000000000000000000001301540713600236525ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/fsm_example/templates/admin/fsm_example/000077500000000000000000000000001301540713600261525ustar00rootroot00000000000000django-fsm-admin-1.2.4/example/fsm_example/templates/admin/fsm_example/change_form.html000066400000000000000000000004201301540713600313040ustar00rootroot00000000000000{% extends 'admin/change_form.html' %} {% load fsm_admin %} {% block submit_buttons_bottom %}{% fsm_submit_row %}{% endblock %} {% block after_field_sets %} {{ block.super }} {% block transition_hints %}{% fsm_transition_hints %}{% endblock %} {% endblock %} django-fsm-admin-1.2.4/example/fsm_example/tests.py000066400000000000000000000000741301540713600223010ustar00rootroot00000000000000from django.test import TestCase # Create your tests here. django-fsm-admin-1.2.4/example/fsm_example/views.py000066400000000000000000000000771301540713600222770ustar00rootroot00000000000000from django.shortcuts import render # Create your views here. django-fsm-admin-1.2.4/example/manage.py000077500000000000000000000003721301540713600200730ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) django-fsm-admin-1.2.4/fsm_admin/000077500000000000000000000000001301540713600165665ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/__init__.py000066400000000000000000000001121301540713600206710ustar00rootroot00000000000000# -*- coding: utf-8 -*- __version__ = '1.2.4' __author__ = 'G Adventures' django-fsm-admin-1.2.4/fsm_admin/locale/000077500000000000000000000000001301540713600200255ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/locale/de/000077500000000000000000000000001301540713600204155ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/locale/de/LC_MESSAGES/000077500000000000000000000000001301540713600222025ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/locale/de/LC_MESSAGES/django.mo000066400000000000000000000013131301540713600237770ustar00rootroot00000000000000Þ•<\p)q'›ÃQØ6*Ga!©%(obj)s successfully set to %(new_state)sError! %(obj)s failed to %(transition)sHints in order to...Project-Id-Version: django-fsm-admin Report-Msgid-Bugs-To: POT-Creation-Date: 2016-03-05 10:21+0100 PO-Revision-Date: 2016-02-29 08:43+0100 Last-Translator: Jonas von Poser Language: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); %(obj)s erfolgreich auf Status "%(new_state)s" gesetztFehler! %(obj)s konnte nicht auf Status "%(transition)s" gesetzt werdenHinweise, um Aktionen auszulösendjango-fsm-admin-1.2.4/fsm_admin/locale/de/LC_MESSAGES/django.po000066400000000000000000000020021301540713600237760ustar00rootroot00000000000000# django-fsm-admin German Translation # Copyright (C) 2016 # This file is distributed under the same license as the django-fsm-admin package. # Jonas von Poser , 2016 # msgid "" msgstr "" "Project-Id-Version: django-fsm-admin\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-03-05 10:21+0100\n" "PO-Revision-Date: 2016-02-29 08:43+0100\n" "Last-Translator: Jonas von Poser \n" "Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" #: mixins.py:99 #, python-format msgid "%(obj)s successfully set to %(new_state)s" msgstr "%(obj)s erfolgreich auf Status \"%(new_state)s\" gesetzt" #: mixins.py:101 #, python-format msgid "Error! %(obj)s failed to %(transition)s" msgstr "" "Fehler! %(obj)s konnte nicht auf Status \"%(transition)s\" gesetzt werden" #: templates/fsm_admin/fsm_transition_hints.html:5 msgid "Hints in order to..." msgstr "Hinweise, um Aktionen auszulösen" django-fsm-admin-1.2.4/fsm_admin/locale/pt_BR/000077500000000000000000000000001301540713600210335ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/locale/pt_BR/LC_MESSAGES/000077500000000000000000000000001301540713600226205ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/locale/pt_BR/LC_MESSAGES/django.mo000066400000000000000000000013401301540713600244150ustar00rootroot00000000000000Þ•<\p)q'›ÕØ0n(ŸÈ%(obj)s successfully set to %(new_state)sError! %(obj)s failed to %(transition)sHints in order to...Project-Id-Version: django-fsm-admin Report-Msgid-Bugs-To: POT-Creation-Date: 2016-03-05 10:21+0100 PO-Revision-Date: 2016-04-12 10:01-0300 Last-Translator: Eloi Aguiar Carneiro da Silva Language: pt_BR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); Language-Team: X-Generator: Poedit 1.8.7.1 %(obj)s definido com êxito para "%(new_state)s"Erro! %(obj)s falhou na "%(transition)s"Sugestões a fim de ...django-fsm-admin-1.2.4/fsm_admin/locale/pt_BR/LC_MESSAGES/django.po000066400000000000000000000020341301540713600244210ustar00rootroot00000000000000# django-fsm-admin German Translation # Copyright (C) 2016 # This file is distributed under the same license as the django-fsm-admin package. # Jonas von Poser , 2016 # msgid "" msgstr "" "Project-Id-Version: django-fsm-admin\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-03-05 10:21+0100\n" "PO-Revision-Date: 2016-04-12 10:01-0300\n" "Last-Translator: Eloi Aguiar Carneiro da Silva \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "Language-Team: \n" "X-Generator: Poedit 1.8.7.1\n" #: mixins.py:99 #, python-format msgid "%(obj)s successfully set to %(new_state)s" msgstr "%(obj)s definido com êxito para \"%(new_state)s\"" #: mixins.py:101 #, python-format msgid "Error! %(obj)s failed to %(transition)s" msgstr "Erro! %(obj)s falhou na \"%(transition)s\"" #: templates/fsm_admin/fsm_transition_hints.html:5 msgid "Hints in order to..." msgstr "Sugestões a fim de ..." django-fsm-admin-1.2.4/fsm_admin/mixins.py000066400000000000000000000237511301540713600204570ustar00rootroot00000000000000from __future__ import unicode_literals from collections import defaultdict from django.conf import settings from django.contrib import messages from django.utils.translation import ugettext as _ from django.utils.encoding import force_text from django.contrib.admin.templatetags.admin_urls import add_preserved_filters from django.http import HttpResponseRedirect class FSMTransitionMixin(object): """ Mixin to use with `admin.ModelAdmin` to support transitioning a model from one state to another (workflow style). * The change_form.html must be overriden to use the custom submit row template (on a model or global level). {% load fsm_admin %} {% block submit_buttons_bottom %}{% fsm_submit_row %}{% endblock %} * To optionally display hints to the user about what's needed to transition to other states that aren't available due to unmet pre-conditions, add this to the change_form as well: {% block after_field_sets %} {{ block.super }} {% fsm_transition_hints %} {% endblock %} * There must be one and only one FSMField on the model. * There must be a corresponding model function to run the transition, generally decorated with the transition decorator. This is what determines the available transitions. Without a function, the action in the submit row will not be available. * In the absence of specific transition permissions, the user must have change permission for the model. """ # Each transition input is named with the state field and transition. # e.g. _fsmtransition-publish_state-publish # _fsmtransition-revision_state-delete fsm_input_prefix = '_fsmtransition' # The name of one or more FSMFields on the model to transition fsm_field = ['state'] change_form_template = 'fsm_admin/change_form.html' default_disallow_transition = not getattr(settings, 'FSM_ADMIN_FORCE_PERMIT', False) def _fsm_get_transitions(self, obj, request, perms=None): """ Gets a list of transitions available to the user. Available state transitions are provided by django-fsm following the pattern get_available_FIELD_transitions """ user = request.user fsm_fields = self._get_fsm_field_list() transitions = {} for field in fsm_fields: transitions_func = 'get_available_user_{0}_transitions'.format(field) transitions_generator = getattr(obj, transitions_func)(user) if obj else [] transitions[field] = self._filter_admin_transitions(transitions_generator) return transitions def get_redirect_url(self, request, obj): """ Hook to adjust the redirect post-save. """ return request.path def fsm_field_instance(self, fsm_field_name): """ Returns the actual state field instance, as opposed to fsm_field attribute representing just the field name. """ return self.model._meta.get_field(fsm_field_name) def display_fsm_field(self, obj, fsm_field_name): """ Makes sure get_FOO_display() is used for choices-based FSM fields. """ field_instance = self.fsm_field_instance(fsm_field_name) if field_instance and field_instance.choices: return getattr(obj, 'get_%s_display' % fsm_field_name)() else: return getattr(obj, fsm_field_name) def response_change(self, request, obj): """ Override of `ModelAdmin.response_change` to detect the FSM button that was clicked in the submit row and perform the state transtion. """ if not getattr(obj, '_fsmtransition_results', None): return super(FSMTransitionMixin, self).response_change(request, obj) if obj._fsmtransition_results['status'] == messages.SUCCESS: msg = _('%(obj)s successfully set to %(new_state)s') % obj._fsmtransition_results else: msg = _('Error! %(obj)s failed to %(transition)s') % obj._fsmtransition_results self.message_user(request, msg, obj._fsmtransition_results['status']) opts = self.model._meta redirect_url = self.get_redirect_url(request=request, obj=obj) preserved_filters = self.get_preserved_filters(request) redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url) return HttpResponseRedirect(redirect_url) def _is_transition_available(self, obj, transition, request): """ Checks if the requested transition is available """ transitions = [] for field, field_transitions in iter(self._fsm_get_transitions(obj, request).items()): transitions += [t.name for t in field_transitions] return transition in transitions def _filter_admin_transitions(self, transitions_generator): """ Filter the given list of transitions, if their transition methods are declared as admin transitions. To allow a transition inside fsm_admin, add the parameter `admin=True` to the transition decorator, for example: ``` @transition(field='state', source=['startstate'], target='finalstate', custom=dict(admin=True)) def do_something(self): ... ``` If the configuration setting `FSM_ADMIN_FORCE_PERMIT = True` then only transitions with `custom=dict(admin=True)` are allowed. Otherwise, if `FSM_ADMIN_FORCE_PERMIT = False` or unset only those with `custom=dict(admin=False)` """ for transition in transitions_generator: if transition.custom.get('admin', self.default_disallow_transition): yield transition def _get_requested_transition(self, request): """ Extracts the name of the transition requested by user """ for key in request.POST.keys(): if key.startswith(self.fsm_input_prefix): fsm_input = key.split('-') return (fsm_input[1], fsm_input[2]) return None, None def _do_transition(self, transition, request, obj, form, fsm_field_name): original_state = self.display_fsm_field(obj, fsm_field_name) msg_dict = { 'obj': force_text(obj), 'transition': transition, 'original_state': original_state, } # Ensure the requested transition is available available = self._is_transition_available(obj, transition, request) trans_func = getattr(obj, transition, None) if available and trans_func: # Run the transition try: # Attempt to pass in the request and by argument if using django-fsm-log trans_func(request=request, by=request.user) except TypeError: try: # Attempt to pass in the by argument if using django-fsm-log trans_func(by=request.user) except TypeError: # If the function does not have a by attribute, just call with no arguments trans_func() new_state = self.display_fsm_field(obj, fsm_field_name) # Mark the fsm_field as changed in the form so it will be # picked up when the change message is constructed form.changed_data.append(fsm_field_name) msg_dict.update({'new_state': new_state, 'status': messages.SUCCESS}) else: msg_dict.update({'status': messages.ERROR}) # Attach the results of our transition attempt setattr(obj, '_fsmtransition_results', msg_dict) def save_model(self, request, obj, form, change): fsm_field, transition = self._get_requested_transition(request) if transition: self._do_transition(transition, request, obj, form, fsm_field) super(FSMTransitionMixin, self).save_model(request, obj, form, change) def get_transition_hints(self, obj): """ See `fsm_transition_hints` templatetag. """ hints = defaultdict(list) transitions = self._get_possible_transitions(obj) # Step through the conditions needed to accomplish the legal state # transitions, and alert the user of any missing condition. # TODO?: find a cleaner way to enumerate conditions methods? for transition in transitions: for condition in transition.conditions: # If the condition is valid, then we don't need the hint if condition(obj): continue # if the transition is hidden, we don't need the hint if transition.custom.get('admin', self.default_disallow_transition): continue hint = getattr(condition, 'hint', '') if hint: if hasattr(transition, 'custom') and transition.custom.get( 'button_name'): hints[transition.custom['button_name']].append(hint) else: hints[transition.name.title()].append(hint) return dict(hints) def _get_possible_transitions(self, obj): """ Get valid state transitions from the current state of `obj` """ fsm_fields = self._get_fsm_field_list() for field in fsm_fields: fsmfield = obj._meta.get_field(field) transitions = fsmfield.get_all_transitions(self.model) for transition in transitions: if transition.source in [getattr(obj, field), '*']: yield transition def _get_fsm_field_list(self): """ Ensure backward compatibility by converting a single fsm field to a list. While we are guaranteeing compatibility we should use this method to retrieve the fsm field rather than directly accessing the property. """ if not isinstance(self.fsm_field, (list, tuple,)): return [self.fsm_field] return self.fsm_field django-fsm-admin-1.2.4/fsm_admin/templates/000077500000000000000000000000001301540713600205645ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/000077500000000000000000000000001301540713600225215ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/change_form.html000066400000000000000000000005041301540713600256560ustar00rootroot00000000000000{% extends 'admin/change_form.html' %} {% load fsm_admin %} {% if save_on_top %}{% block submit_buttons_top %}{% fsm_submit_row %}{% endblock %}{% endif %} {% block submit_buttons_bottom %}{% fsm_submit_row %}{% endblock %} {% block after_field_sets %} {{ block.super }} {% fsm_transition_hints %} {% endblock %} django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_button.html000066400000000000000000000002441301540713600271520ustar00rootroot00000000000000 django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_button_grappelli.html000066400000000000000000000003341301540713600312110ustar00rootroot00000000000000
  • django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_button_suit.html000066400000000000000000000002471301540713600302210ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_button_wpadmin.html000066400000000000000000000002431301540713600306700ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_line.html000066400000000000000000000015661301540713600265760ustar00rootroot00000000000000{% load i18n admin_urls fsm_admin %}
    {% if show_save %}{% endif %} {% if show_delete_link %} {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %} {% endif %} {% if show_save_as_new %}{%endif%} {% if show_save_and_add_another %}{% endif %} {% if show_save_and_continue %}{% endif %} {% for transition in transitions %} {% fsm_submit_button transition %} {% endfor %}
    django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_line_grappelli.html000066400000000000000000000025101301540713600306230ustar00rootroot00000000000000{% load i18n admin_urls fsm_admin %}

    Submit Options

      {% if show_delete_link %} {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
    • {% trans "Delete" %}
    • {% endif %} {% if show_save %}
    • {% endif %} {% if show_save_as_new %}
    • {% endif %} {% if show_save_and_add_another %}
    • {% endif %} {% if show_save_and_continue %}
    • {% endif %} {% for transition in transitions %} {% fsm_submit_button transition %} {% endfor %}

    django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_line_suit.html000066400000000000000000000016321301540713600276340ustar00rootroot00000000000000{% load i18n fsm_admin %}
    {% if show_save %}{% endif %} {% if show_save_and_continue %}{% endif %} {% if show_save_as_new %}{%endif%} {% if show_save_and_add_another %} {% endif %} {% for transition in transitions %} {% fsm_submit_button transition %} {% endfor %} {% if show_delete_link %}{% trans "Delete" %} {% endif %}
    django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_submit_line_wpadmin.html000066400000000000000000000033771301540713600303170ustar00rootroot00000000000000{% load i18n admin_urls fsm_admin %}
    {% if show_save %}{% endif %} {% if show_save_as_new %}{%endif%} {% if show_save_and_add_another %}{% endif %} {% if show_save_and_continue %}{% endif %} {% if show_delete_link %} {% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %} {% trans "Delete" %} {% endif %}
    {% for transition in transitions %} {% fsm_submit_button transition %} {% endfor %}
    django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_transition_hints.html000066400000000000000000000010071301540713600276510ustar00rootroot00000000000000{% load i18n %} {% if transition_hints %}

    {% trans "Hints in order to..." %}

    {% for action, hints in transition_hints.items %} {% for hint in hints %}
    {% if forloop.first %} {% endif %}

    {{ hint }}

    {% endfor %} {% endfor %}
    {% endif %} django-fsm-admin-1.2.4/fsm_admin/templates/fsm_admin/fsm_transition_hints_suit.html000066400000000000000000000014521301540713600307210ustar00rootroot00000000000000{% load i18n %} {% if transition_hints %}

    {% trans "Hints in order to..." %}

    {% for action, hints in transition_hints.items %} {% for hint in hints %} {% if forloop.first %}

    {% endif %} {{ hint }} {% if forloop.last %}

    {% endif %} {% endfor %} {% endfor %}
    {% endif %} django-fsm-admin-1.2.4/fsm_admin/templatetags/000077500000000000000000000000001301540713600212605ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/templatetags/__init__.py000066400000000000000000000000001301540713600233570ustar00rootroot00000000000000django-fsm-admin-1.2.4/fsm_admin/templatetags/fsm_admin.py000066400000000000000000000070151301540713600235720ustar00rootroot00000000000000from __future__ import unicode_literals import logging from django import template from django.contrib.admin.templatetags.admin_modify import submit_row from django.conf import settings from django.db import models register = template.Library() logger = logging.getLogger(__name__) FSM_SUBMIT_BUTTON_TEMPLATE = 'fsm_admin/fsm_submit_button.html' FSM_SUBMIT_LINE_TEMPLATE = 'fsm_admin/fsm_submit_line.html' FSM_TRANSITION_HINTS = 'fsm_admin/fsm_transition_hints.html' if 'grappelli' in settings.INSTALLED_APPS: FSM_SUBMIT_BUTTON_TEMPLATE = 'fsm_admin/fsm_submit_button_grappelli.html' FSM_SUBMIT_LINE_TEMPLATE = 'fsm_admin/fsm_submit_line_grappelli.html' if 'suit' in settings.INSTALLED_APPS: FSM_SUBMIT_BUTTON_TEMPLATE = 'fsm_admin/fsm_submit_button_suit.html' FSM_SUBMIT_LINE_TEMPLATE = 'fsm_admin/fsm_submit_line_suit.html' FSM_TRANSITION_HINTS = 'fsm_admin/fsm_transition_hints_suit.html' if 'wpadmin' in settings.INSTALLED_APPS: FSM_SUBMIT_BUTTON_TEMPLATE = 'fsm_admin/fsm_submit_button_wpadmin.html' FSM_SUBMIT_LINE_TEMPLATE = 'fsm_admin/fsm_submit_line_wpadmin.html' @register.inclusion_tag(FSM_SUBMIT_BUTTON_TEMPLATE) def fsm_submit_button(transition): """ Render a submit button that requests an fsm state transition for a single state. """ fsm_field_name, button_value, transition_name = transition return { 'button_value': button_value, 'fsm_field_name': fsm_field_name, 'transition_name': transition_name, } @register.inclusion_tag(FSM_SUBMIT_LINE_TEMPLATE, takes_context=True) def fsm_submit_row(context): """ Additional context added to an overridded submit row that adds links to change the state of an FSMField. """ original = context.get('original', None) model_name = '' if original is not None: # TODO: replace the following line by `original is models.DEFERRED` # after dropping support for Django-1.9 if getattr(original, '_deferred', False) or original is getattr(models, 'DEFERRED', None): model_name = type(original).__base__._meta.verbose_name else: model_name = original.__class__._meta.verbose_name def button_name(transition): if hasattr(transition, 'custom') and 'button_name' in transition.custom: return transition.custom['button_name'] else: # Make the function name the button title, but prettier return '{0} {1}'.format(transition.name.replace('_', ' '), model_name).title() # The model admin defines which field we're dealing with # and has some utils for getting the transitions. request = context['request'] model_admin = context.get('adminform').model_admin transitions = model_admin._fsm_get_transitions(original, request) ctx = submit_row(context) ctx['transitions'] = [] for field, field_transitions in iter(transitions.items()): ctx['transitions'] += sorted( [(field, button_name(t), t.name) for t in field_transitions], key=lambda e: e[1], reverse=True ) ctx['perms'] = context['perms'] return ctx @register.inclusion_tag(FSM_TRANSITION_HINTS, takes_context=True) def fsm_transition_hints(context): """ Displays hints about why a state transition might not be applicable for this the model. """ original = context.get('original', None) if not original: return {} model_admin = context.get('adminform').model_admin return { 'transition_hints': model_admin.get_transition_hints(original) } django-fsm-admin-1.2.4/requirements.txt000066400000000000000000000000361301540713600201140ustar00rootroot00000000000000Django>=1.6 django-fsm==2.0.1 django-fsm-admin-1.2.4/setup.cfg000066400000000000000000000000261301540713600164500ustar00rootroot00000000000000[wheel] universal = 1 django-fsm-admin-1.2.4/setup.py000066400000000000000000000024521301540713600163460ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys from setuptools import setup, find_packages import fsm_admin if sys.argv[-1] == 'publish': os.system('python setup.py sdist upload') sys.exit() with open('README.rst') as f: readme = f.read() setup( name='django-fsm-admin', version=fsm_admin.__version__, author=fsm_admin.__author__, description='Integrate django-fsm state transitions into the django admin', long_description=readme, author_email='software@gadventures.com', url='https://github.com/gadventures/django-fsm-admin', packages=find_packages(), include_package_data=True, install_requires=[ 'Django>=1.6', 'django-fsm>=2.1.0', ], keywords='django', license='MIT License', platforms=['any'], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Web Environment', 'Framework :: Django', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', ] )