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

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

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

# Copyright 2010-2015 Luc Saffre 

# License: BSD (see file COPYING for details) 

 

"""This package contains Model mixins, some of which are heavily used 

by :mod:`lino.modlib`. None of them is mandatory for a Lino 

application. 

 

.. autosummary:: 

   :toctree: 

 

    duplicable 

    dupable 

    sequenced 

    human 

    periods 

    polymorphic 

    uploadable 

 

Parameter panels: 

 

- :class:`ObservedPeriod <lino.mixins.periods.ObservedPeriod>` 

- :class:`Yearly <lino.mixins.periods.Yearly>` 

- :class:`Today <lino.mixins.periods.Today>` 

 

   

 

""" 

 

from __future__ import unicode_literals 

from builtins import str 

from builtins import object 

 

import logging 

logger = logging.getLogger(__name__) 

 

 

from django.db import models 

from django.conf import settings 

from django.utils.translation import ugettext_lazy as _ 

from django.utils import timezone 

from django.contrib.humanize.templatetags.humanize import naturaltime 

 

 

from lino.core import fields 

from lino.core import model 

 

from lino.core.workflows import ChangeStateAction 

 

 

class Registrable(model.Model): 

 

    """ 

    Base class to anything that may be "registered" and "deregistered" 

    (e.g. Invoices, Vouchers, Declarations, Reservations,...). 

    "Registered" in general means "this object has been taken account of". 

    Registered objects are not editable. 

 

    .. attribute:: state 

 

        The ChoiceList of the `state` field must have at least two items 

        named "draft" and "registered". 

        There may be additional states. 

        Every state must have an extra attribute "editable". 

 

    """ 

    class Meta(object): 

        abstract = True 

 

    workflow_state_field = 'state' 

 

    _registrable_fields = None 

 

    @classmethod 

    def get_registrable_fields(cls, site): 

        """Return a list of the fields which are *disabled* when this is 

        *registered* (i.e. `state` is not `editable`). 

 

        Usage example:: 

 

            class MyModel(dd.Registrable): 

 

                @classmethod 

                def get_registrable_fields(self, site): 

                    for f in super(MyModel, self).get_registrable_fields(site): 

                        yield f 

                    yield 'user' 

                    yield 'date' 

 

 

        """ 

        return [] 

        #~ yield 'date' 

 

    @classmethod 

    def on_analyze(cls, site): 

        super(Registrable, cls).on_analyze(site) 

        cls._registrable_fields = set(cls.get_registrable_fields(site)) 

        #~ logger.info("20130128 %s %s",cls,cls._registrable_fields) 

 

    def disabled_fields(self, ar): 

        if not self.state.editable: 

            return self._registrable_fields 

        return super(Registrable, self).disabled_fields(ar) 

 

    def get_row_permission(self, ar, state, ba): 

        """Only rows in an editable state may be edited. 

 

        Note that `ba` is the action being requested while 

        `ar.bound_action` is the action from which the request was 

        started. 

 

        """ 

        # print "20150628 Registrable.get_row_permission %s %s : %s %s" \ 

        #     % (self, ba, state.editable, ba.action.readonly) 

        if state and not state.editable and not isinstance( 

                ba.action, ChangeStateAction): 

            # if not ar.bound_action.action.readonly: 

            if not ba.action.readonly: 

                return False 

        return super(Registrable, self).get_row_permission(ar, state, ba) 

 

    def register(self, ar): 

        """Register this object.  The base implementation just sets the state 

        to "registered". 

 

        Subclasses may override this to add custom behaviour.  Instead 

        of subclassing you can also override :meth:`set_workflow_state 

        <lino.core.model.Model.set_workflow_state>`, 

        :meth:`before_state_change 

        <lino.core.model.Model.before_state_change>` or 

        :meth:`after_state_change 

        <lino.core.model.Model.after_state_change>`. 

 

        """ 

 

        state_field = self._meta.get_field(self.workflow_state_field) 

        target_state = state_field.choicelist.registered 

        self.set_workflow_state(ar, state_field, target_state) 

 

    def deregister(self, ar): 

        """Deregister this object.  The base implementation just sets the 

        state to "draft". 

 

        Subclasses may override this to add custom behaviour.  Instead 

        of subclassing you can also override :meth:`set_workflow_state 

        <lino.core.model.Model.set_workflow_state>`, 

        :meth:`before_state_change 

        <lino.core.model.Model.before_state_change>` or 

        :meth:`after_state_change 

        <lino.core.model.Model.after_state_change>`. 

 

        """ 

 

        state_field = self._meta.get_field(self.workflow_state_field) 

        target_state = state_field.choicelist.draft 

        self.set_workflow_state(ar, state_field, target_state) 

 

 

class Modified(model.Model): 

 

    class Meta(object): 

        abstract = True 

 

    modified = models.DateTimeField(_("Modified"), editable=False) 

 

    def save(self, *args, **kwargs): 

        if not settings.SITE.loading_from_dump: 

            self.touch() 

        super(Modified, self).save(*args, **kwargs) 

 

    def touch(self): 

        self.modified = timezone.now() 

 

 

class Created(model.Model): 

    """Mixin for models which have a field :attr:`created`  

 

    .. attribute:: created 

 

        The timestame when this object was created. 

 

    """ 

    class Meta(object): 

        abstract = True 

 

    created = models.DateTimeField(_("Created"), editable=False) 

 

    @fields.displayfield(_('Created')) 

    def created_natural(self, ar): 

        return naturaltime(self.created) 

 

    def save(self, *args, **kwargs): 

        if self.created is None and not settings.SITE.loading_from_dump: 

            self.created = timezone.now() 

        super(Created, self).save(*args, **kwargs) 

 

 

class CreatedModified(Created, Modified): 

 

    """Adds two timestamp fields `created` and `modified`. 

 

    We don't use Django's `auto_now` and `auto_now_add` features 

    because their deserialization (restore from a python dump) would 

    be problematic. 

 

    """ 

 

    class Meta(object): 

        abstract = True 

 

 

class ProjectRelated(model.Model): 

 

    """Mixin for Models that are automatically related to a "project".  A 

    project means here "the central most important thing that is used 

    to classify most other things".   

 

    Whether an application has such a concept of "project", 

    and which model has this privileged status, 

    is set in :attr:`lino.core.site.Site.project_model`. 

 

    For example in :ref:`welfare` the "project" is a Client. 

 

    """ 

 

    class Meta(object): 

        abstract = True 

 

    if settings.SITE.project_model: 

        project = models.ForeignKey( 

            settings.SITE.project_model, 

            blank=True, null=True, 

            related_name="%(app_label)s_%(class)s_set_by_project", 

        ) 

    else: 

        project = fields.DummyField() 

 

    def get_related_project(self): 

        if settings.SITE.project_model: 

            return self.project 

 

    #~ def summary_row(self,ui,rr,**kw): 

    def summary_row(self, ar, **kw): 

        s = [ar.obj2html(self)] 

        if settings.SITE.project_model: 

            #~ if self.project and not dd.has_fk(rr,'project'): 

            if self.project: 

                #~ s += " (" + ui.obj2html(self.project) + ")" 

                s += [" (", ar.obj2html(self.project), ")"] 

        return s 

 

    def update_owned_instance(self, controllable): 

        """ 

        When a :class:`project-related <ProjectRelated>` 

        object controls another project-related object, 

        then the controlled automatically inherits 

        the `project` of its controller. 

        """ 

        if isinstance(controllable, ProjectRelated): 

            controllable.project = self.project 

        super(ProjectRelated, self).update_owned_instance(controllable) 

 

    def get_mailable_recipients(self): 

        if isinstance(self.project, settings.SITE.modules.contacts.Partner): 

            if self.project.email: 

                yield ('to', self.project) 

        for r in super(ProjectRelated, self).get_mailable_recipients(): 

            yield r 

 

    def get_postable_recipients(self): 

        if isinstance(self.project, settings.SITE.modules.contacts.Partner): 

            yield self.project 

        for p in super(ProjectRelated, self).get_postable_recipients(): 

            yield p 

 

 

class Referrable(model.Model): 

    """ 

    Mixin for things that have a unique `ref` field and a `get_by_ref` method. 

    """ 

    class Meta(object): 

        abstract = True 

 

    ref_max_length = 40 

 

    ref = fields.NullCharField(_("Reference"), 

                               max_length=ref_max_length, 

                               blank=True, null=True, 

                               unique=True) 

 

    def on_duplicate(self, ar, master): 

        """After duplicating we must change the :attr:`ref`. 

 

        """ 

        if self.ref: 

            self.ref += '(DUP)' 

        super(Referrable, self).on_duplicate(ar, master) 

 

    @classmethod 

    def get_by_ref(cls, ref, default=models.NOT_PROVIDED): 

        try: 

            return cls.objects.get(ref=ref) 

        except cls.DoesNotExist: 

            if default is models.NOT_PROVIDED: 

                raise cls.DoesNotExist( 

                    "No %s with reference %r" % (str(cls._meta.verbose_name), ref)) 

            return default 

 

    #~ def __unicode__(self): 

        #~ return self.ref or unicode(_('(Root)')) 

 

    # def __unicode__(self): 

    #     return super(Referrable, self).__unicode__() + " (" + self.ref + ")" 

        # return unicode(super(Referrable, self)) + " (" + self.ref + ")" 

 

 

 

from lino.modlib.printing.mixins import ( 

    Printable, PrintableType, CachedPrintable, TypedPrintable, 

    DirectPrintAction, CachedPrintAction) 

 

from lino.mixins.duplicable import Duplicable, Duplicate 

from lino.mixins.sequenced import Sequenced, Hierarchical 

from lino.mixins.periods import DatePeriod 

from lino.mixins.periods import ObservedPeriod, Yearly, Today 

from lino.mixins.polymorphic import Polymorphic 

from lino.mixins.uploadable import Uploadable 

 

from lino.utils.mldbc.fields import BabelCharField, BabelTextField 

from lino.utils.mldbc.mixins import BabelNamed 

 

from lino.mixins.human import Human, Born