Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

# -*- coding: UTF-8 -*- 

# Copyright 2012-2016 Luc Saffre 

# License: BSD (see file COPYING for details) 

"""Defines the classes used for generating workflows: 

:class:`State` and :class:`Workflow`, :class:`ChangeStateAction`. 

 

""" 

from builtins import str 

# import six 

# str = six.text_type 

 

from past.builtins import basestring 

 

import logging 

logger = logging.getLogger(__name__) 

 

from django.utils.functional import Promise 

from django.utils.translation import ugettext_lazy as _ 

from django.utils.translation import string_concat 

 

from lino.core import actions 

from lino.core import choicelists 

 

# from django.utils.encoding import force_text 

# from django.utils.functional import lazy 

# def _string_format(tpl, *args, **kwargs): 

#     args = tuple([force_text(s) for s in args]) 

#     return tpl.format(*args, **kwargs) 

# string_format = lazy(_string_format, basestring) 

 

 

class State(choicelists.Choice): 

    """A `State` is a specialized :class:`Choice 

    <lino.core.choicelists.Choice>` that adds the 

    :meth:`add_transition` method. 

 

    """ 

 

    def add_transition(self, label=None, 

                       help_text=None, 

                       notify=False, 

                       name=None, 

                       #~ icon_file=None, 

                       icon_name=None, 

                       debug_permissions=None, 

                       required_states=None, 

                       required_roles=None): 

        """Declare or create a `ChangeStateAction` which makes the object 

        enter this state.  `label` can be either a string or a 

        subclass of :class:`ChangeStateAction`. 

 

        You can specify an explicit `name` in order to allow replacing 

        the transition action later by another action. 

 

        """ 

        workflow_actions = self.choicelist.workflow_actions 

        i = len(workflow_actions) 

        if name is None: 

            #~ name = 'mark_' + self.value 

            name = 'wf' + str(i + 1) 

 

        for x in self.choicelist.workflow_actions: 

            if x.action_name == name: 

                raise Exception( 

                    "Duplicate transition name {0}".format(name)) 

 

        kw = dict() 

        if help_text is not None: 

            kw.update(help_text=help_text) 

        if icon_name is not None: 

            kw.update(icon_name=icon_name) 

        kw.update(sort_index=10 + i) 

        if label is not None and not isinstance(label, (basestring, Promise)): 

            # it's a subclass of ChangeStateAction 

            assert isinstance(label, type) 

            assert issubclass(label, ChangeStateAction) 

            if required_roles: 

                raise Exception( 

                    "Cannot specify requirements when using your own class") 

            if required_states: 

                raise Exception( 

                    "Cannot specify requirements when using your own class") 

            if notify: 

                raise Exception( 

                    "Cannot specify notify=True when using your own class") 

            if debug_permissions: 

                raise Exception( 

                    "Cannot specify debug_permissions " 

                    "when using your own class") 

            for a in workflow_actions: 

                if isinstance(a, label): 

                    raise Exception("Duplicate transition label %s" % a) 

            a = label(self, **kw) 

        else: 

            if required_states: 

                kw.update(required_states=required_states) 

            if notify: 

                cl = NotifyingChangeStateAction 

            else: 

                cl = ChangeStateAction 

            if label is None: 

                label = self.text 

            a = cl(self, required_roles, label=label, **kw) 

            if debug_permissions: 

                a.debug_permissions = debug_permissions 

        a.attach_to_workflow(self.choicelist, name) 

 

        self.choicelist.workflow_actions = workflow_actions + [a] 

 

    add_workflow = add_transition  # backwards compat 

 

 

class Workflow(choicelists.ChoiceList): 

 

    """A Workflow is a specialized ChoiceList used for defining the 

    states of a workflow. 

 

    """ 

    item_class = State 

 

    verbose_name = _("State") 

    verbose_name_plural = _("States") 

 

    @classmethod 

    def on_analyze(cls, site): 

        """Add workflow actions to the models using this workflow so that we 

        can access them as InstanceActions. 

 

        """ 

        super(Workflow, cls).on_analyze(site) 

        # logger.info("20150602 Workflow.on_analyze %s", cls) 

        for fld in cls._fields: 

            model = getattr(fld, 'model', None) 

            if model: 

                # logger.info("20150602 %s, %s", model, cls.workflow_actions) 

                for a in cls.workflow_actions: 

                    # if not a.action_name.startswith('wf'): 

                    if not hasattr(model, a.action_name): 

                        setattr(model, a.action_name, a) 

 

    @classmethod 

    def before_state_change(cls, obj, ar, oldstate, newstate): 

        pass 

 

    @classmethod 

    def after_state_change(cls, obj, ar, oldstate, newstate): 

        pass 

 

    @classmethod 

    def override_transition(cls, **kw): 

        """ 

        """ 

        for name, cl in list(kw.items()): 

            found = False 

            for i, a in enumerate(cls.workflow_actions): 

                if a.action_name == name: 

                    new = cl( 

                        a.target_state, a.required_roles, 

                        sort_index=a.sort_index) 

                    new.attach_to_workflow(cls, name) 

                    cls.workflow_actions[i] = new 

                    found = True 

                    break 

            if not found: 

                raise Exception( 

                    "There is no workflow action named {0}".format(name)) 

 

 

class ChangeStateAction(actions.Action): 

    """This is the class used when generating automatic "state 

    actions". For each possible value of the Actor's 

    :attr:`workflow_state_field` there will be an automatic action 

    called `mark_XXX` 

 

    """ 

 

    show_in_bbar = False 

    show_in_workflow = True 

    readonly = False 

 

    def __init__(self, target_state, required_roles=None, 

                 help_text=None, **kw): 

        self.target_state = target_state 

        assert 'required' not in kw 

        assert 'required_roles' not in kw 

        new_required = set(self.required_roles) 

        if required_roles is not None: 

            new_required |= required_roles 

        if target_state.name: 

 

            m = getattr(target_state.choicelist, 'allow_transition', None) 

            if m is not None: 

                raise Exception("20150621 was allow_transition still used?!") 

                assert 'allowed' not in required_roles 

 

                def allow(action, user, obj, state): 

                    return m(obj, user, target_state) 

                new_required.update(allow=allow) 

 

        kw.update(required_roles=new_required) 

        if self.help_text is None: 

            if help_text is None: 

                # help_text = string_format( 

                #     _("Mark this as {0}"), target_state.text) 

                help_text = string_concat( 

                    _("Mark this as"), ' ', target_state.text) 

            kw.update(help_text=help_text) 

 

        super(ChangeStateAction, self).__init__(**kw) 

        #~ logger.info('20120930 ChangeStateAction %s %s', actor,target_state) 

        if self.icon_name: 

            self.help_text = string_concat(self.label, '. ', self.help_text) 

 

    def run_from_ui(self, ar): 

        for row in ar.selected_rows: 

            self.execute(ar, row) 

        ar.set_response(refresh=True) 

        ar.success() 

 

    def execute(self, ar, obj): 

        return obj.set_workflow_state( 

            ar, 

            ar.actor.workflow_state_field, 

            self.target_state) 

 

 

class NotifyingChangeStateAction(ChangeStateAction, actions.NotifyingAction): 

    pass