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

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

# Copyright 2011-2015 Luc Saffre 

# License: BSD (see file COPYING for details) 

 

"""Database models for `lino.modlib.notifier`. 

 

A notification is a message to be sent to a given user about a given 

database object. Lino 

 

 

.. xfile:: notifier/body.eml 

 

    The Jinja template to use for generating the body of the 

    notification email. 

 

    Available context variables: 

 

    :obj:  The :class:`Notification` object 

    :E:    The html namespace :mod:`lino.utils.xmlgen.html` 

    :rt:   The runtime API :mod:`lino.api.rt` 

    :ar:   The action request which caused the notification. a 

           :class:`BaseRequest <lino.core.requests.BaseRequest>`) 

 

""" 

from builtins import str 

from builtins import object 

 

from django.db import models 

from django.conf import settings 

from django.utils import timezone 

 

from lino.api import dd, rt, _ 

 

from lino.core.roles import SiteStaff 

from lino.core.gfks import gfk2lookup 

from lino.core.requests import BaseRequest 

 

from lino.mixins import Created 

from lino.modlib.gfks.mixins import Controllable 

from lino.modlib.users.mixins import UserAuthored, My 

 

from lino.utils.xmlgen.html import E 

from lino.utils import join_elems 

 

 

@dd.python_2_unicode_compatible 

class Notification(UserAuthored, Controllable, Created): 

    """A **notification** object represents the fact that a given user has 

    been notified about a given database object. 

     

    Use the class method :meth:`notify` to create a new notification 

    (and to skip creation in case that user has already been notified 

    about that object) 

 

    .. attribute:: message 

     

        The message to display. This should be a plain string which 

        will be formatted (using standard string format) using the 

        following context: 

 

        :obj:  the object attached to this notification 

        :user: the user who caused this notification 

 

    .. attribute:: overview 

 

        A display field which returns the parsed :attr:`message`. 

 

    """ 

    class Meta(object): 

        app_label = 'notifier' 

        verbose_name = _("Notification") 

        verbose_name_plural = _("Notifications") 

 

    seen = models.DateTimeField(_("seen"), null=True, editable=False) 

    message = models.TextField(_("Message"), editable=False) 

 

    def __str__(self): 

        return _("About {0}").format(self.owner) 

        # return self.message 

        # return _("Notify {0} about change on {1}").format( 

        #     self.user, self.owner) 

 

    @classmethod 

    def notify(cls, ar, owner, user, message): 

        """Create a notification unless that user has already been notified 

        about that object. 

 

 

        """ 

        fltkw = gfk2lookup(cls.owner, owner) 

        qs = cls.objects.filter( 

            user=user, seen__isnull=True, **fltkw) 

        if not qs.exists(): 

            # create a notification object and send email 

            obj = cls(user=user, owner=owner, message=message) 

            obj.full_clean() 

            obj.save() 

            obj.send_email(ar) 

 

    @dd.displayfield() 

    def overview(self, ar): 

        if ar is None: 

            return '' 

        return self.get_overview(ar) 

 

    def get_overview(self, ar): 

        """Return the content to be displayed in the :attr:`overview` field. 

        On interactive rendererers (extjs, bootstrap3) the `obj` and 

        `user` are clickable. 

 

        This is also used from the :xfile:`notifier/body.eml` template 

        where they should just be surrounded by **double asterisks** 

        so that Thunderbird displays them bold. 

 

        """ 

        context = dict( 

            obj=ar.obj2str(self.owner), 

            user=ar.obj2str(self.user)) 

        return _(self.message).format(**context) 

        # return E.p( 

        #     ar.obj2html(self.owner), " ", 

        #     _("was modified by {0}").format(self.user)) 

 

    @dd.action() 

    def send_email(self, ar): 

        if not self.user.email: 

            dd.logger.info("User %s has no email address", self.user) 

            return 

        # dd.logger.info("20151116 %s %s", ar.bound_action, ar.actor) 

        # ar = ar.spawn_request(renderer=dd.plugins.bootstrap3.renderer) 

        sar = BaseRequest( 

            # user=self.user, renderer=dd.plugins.bootstrap3.renderer) 

            user=self.user, renderer=settings.SITE.kernel.text_renderer) 

        tpl = dd.plugins.notifier.email_subject_template 

        subject = tpl.format(obj=self) 

        subject = settings.EMAIL_SUBJECT_PREFIX + subject 

        template = rt.get_template('notifier/body.eml') 

        context = dict(obj=self, E=E, rt=rt, ar=sar) 

        body = template.render(**context) 

        sender_email = ar.get_user().email or settings.SERVER_EMAIL 

        sender = "{0} <{1}>".format(ar.get_user(), sender_email) 

        rt.send_email( 

            subject, sender, body, [self.user.email]) 

 

dd.update_field(Notification, 'user', verbose_name=_("User")) 

 

 

class Notifications(dd.Table): 

    """Shows the gobal list of all notifications. 

 

    """ 

    model = 'notifier.Notification' 

    column_names = "created overview user seen *" 

 

    detail_layout = """ 

    overview 

    notifier.ChangesByNotification 

    """ 

 

    @classmethod 

    def get_detail_title(self, ar, obj): 

        if obj.seen is None and obj.user == ar.get_user(): 

            obj.seen = timezone.now() 

            obj.save() 

            # dd.logger.info("20151115 Marked %s as seen", obj) 

        return super(Notifications, self).get_detail_title(ar, obj) 

 

 

class AllNotifications(Notifications): 

    required_roles = dd.required(SiteStaff) 

 

 

class MyNotifications(My, Notifications): 

    required_roles = dd.required() 

 

 

def welcome_messages(ar): 

    """Yield messages for the welcome page.""" 

 

    Notification = rt.modules.notifier.Notification 

    qs = Notification.objects.filter(user=ar.get_user(), seen__isnull=True) 

    if qs.count() > 0: 

        chunks = [ 

            str(_("You have %d unseen notifications: ")) % qs.count()] 

        chunks += join_elems([ 

            ar.obj2html(obj, str(obj.owner)) for obj in qs]) 

        yield E.span(*chunks) 

 

dd.add_welcome_handler(welcome_messages) 

 

 

if dd.is_installed('changes'): 

 

    from lino.modlib.changes.models import ChangesByMaster 

 

    class ChangesByNotification(ChangesByMaster): 

 

        master = 'notifier.Notification' 

 

        @classmethod 

        def get_request_queryset(cls, ar): 

            mi = ar.master_instance 

            if mi is None: 

                return cls.model.objects.null() 

            return cls.model.objects.filter( 

                time__gte=mi.created, 

                **gfk2lookup(cls.model.master, mi.owner)) 

 

else: 

 

    ChangesByNotification = None