django+mongoenine使用Signals信号

最近做的django项目数据库用的是mongodb,orm使用的是mongengine

有个需求是想把某一个model的数据库update操作都记录下,仅仅记录改变的字段和值,最终决定使用信号机制来做这个事情

根据官方文档要求需要安装 blinker 库

可使用的信号有:

1 post_init:在 Document 或者 EmbeddedDocument 实例对象初始化完成之后调用。
2 pre_init: 在创建一个新的 Document 或者 EmbeddedDocument 实例对象之后,并且对象初始化之前调用。
3 pre_save:在 save 方法执行之前调用。
4 pre_save_post_validation:在数据检验完成之后,数据保存之前调用。
5 post_save:在数据保存完成之后调用。
6 pre_delete:在 delete 方法执行之前调用。
7 post_delete:在记录成功删除之后调用。
8 pre_bulk_insert:在数据检验之后,数据插入之前调用。
9 post_bulk_insert:在数据成功插入之后调用。

1、根据实际需求使用了post_save信号,models.py 如下:

  1 from mongoengine import *
  2 from mongoengine import Q as Qm
  3 from mongoengine.base.document import BaseDocument
  4 
  5 class Sheet(DynamicDocument):
  6     """需要保存修改记录的表"""
  7     answerby = ListField(EmbeddedDocumentField(OperMan,verbose_name=u'报价应答'))
  8     replyby = ListField(EmbeddedDocumentField(OperMan,verbose_name=u'联络单回复'))
  9     modifytime = DateTimeField(verbose_name=u'修改时间')
 10     checktime = DateTimeField(verbose_name=u'审核时间')
 11     uptime = DateTimeField(verbose_name=u'上传税务局时间')
 12     addtime = DateTimeField(verbose_name=u'创建时间',default=datetime.datetime.now)
 13     grabtime = DateTimeField(verbose_name=u'用户抢单时间')
 14     despatchactualdatetime = DateTimeField(verbose_name=u'发运实际日期时间')
 15     goodsreceiptdatetime = DateTimeField(verbose_name=u'收货日期时间')
 16     difffeetime = DateTimeField(verbose_name=u'调用费用变更日期时间') # 用于计算里程
 17     completetime = DateTimeField(verbose_name=u'作业完成时间')
 18     lastpaytime = DateTimeField(verbose_name=u'最后一次支付请求时间')
 19     paidtotime = DateTimeField(verbose_name=u'付款给司机时间')
 20     oilpaidtime = DateTimeField(verbose_name=u'付中石化油款给司机时间')
 21     cnpcoilpaidtime = DateTimeField(verbose_name=u'付中石油油款给司机时间')
 22     offlineoilpaidtime = DateTimeField(verbose_name=u'付线下油款给司机时间')
 23     etcpaidtime = DateTimeField(verbose_name=u'付etc款给司机时间')
 24     offlineetcpaidtime = DateTimeField(verbose_name=u'付线下etc款给司机时间')
 25     paydriveretcpaidtime = DateTimeField(verbose_name=u'现付etc款给司机时间')
 26     prepaidtotime = DateTimeField(verbose_name=u'预付款给司机时间')
 27     preoilpaidtime = DateTimeField(verbose_name=u'预付中石化油款给司机时间')
 28     precnpcoilpaidtime = DateTimeField(verbose_name=u'预付中石油油款给司机时间')
 29     preofflineoilpaidtime = DateTimeField(verbose_name=u'预付线下油款给司机时间')
 30     preetcpaidtime = DateTimeField(verbose_name=u'预付etc款给司机时间')
 31     preofflineetcpaidtime = DateTimeField(verbose_name=u'预付线下etc款给司机时间')
 32     
 33     def save(self, force_insert=False, validate=True, clean=True,
 34              write_concern=None, cascade=None, cascade_kwargs=None,
 35              _refs=None, save_condition=None, signal_kwargs=None, **kwargs):
 36         """
 37         Save the :class:`~mongoengine.Document` to the database. If the
 38         document already exists, it will be updated, otherwise it will be
 39         created.
 40 
 41         :param force_insert: only try to create a new document, don't allow
 42             updates of existing documents.
 43         :param validate: validates the document; set to ``False`` to skip.
 44         :param clean: call the document clean method, requires `validate` to be
 45             True.
 46         :param write_concern: Extra keyword arguments are passed down to
 47             :meth:`~pymongo.collection.Collection.save` OR
 48             :meth:`~pymongo.collection.Collection.insert`
 49             which will be used as options for the resultant
 50             ``getLastError`` command.  For example,
 51             ``save(..., write_concern={w: 2, fsync: True}, ...)`` will
 52             wait until at least two servers have recorded the write and
 53             will force an fsync on the primary server.
 54         :param cascade: Sets the flag for cascading saves.  You can set a
 55             default by setting "cascade" in the document __meta__
 56         :param cascade_kwargs: (optional) kwargs dictionary to be passed throw
 57             to cascading saves.  Implies ``cascade=True``.
 58         :param _refs: A list of processed references used in cascading saves
 59         :param save_condition: only perform save if matching record in db
 60             satisfies condition(s) (e.g. version number).
 61             Raises :class:`OperationError` if the conditions are not satisfied
 62         :param signal_kwargs: (optional) kwargs dictionary to be passed to
 63             the signal calls.
 64 
 65         .. versionchanged:: 0.5
 66             In existing documents it only saves changed fields using
 67             set / unset.  Saves are cascaded and any
 68             :class:`~bson.dbref.DBRef` objects that have changes are
 69             saved as well.
 70         .. versionchanged:: 0.6
 71             Added cascading saves
 72         .. versionchanged:: 0.8
 73             Cascade saves are optional and default to False.  If you want
 74             fine grain control then you can turn off using document
 75             meta['cascade'] = True.  Also you can pass different kwargs to
 76             the cascade save using cascade_kwargs which overwrites the
 77             existing kwargs with custom values.
 78         .. versionchanged:: 0.8.5
 79             Optional save_condition that only overwrites existing documents
 80             if the condition is satisfied in the current db record.
 81         .. versionchanged:: 0.10
 82             :class:`OperationError` exception raised if save_condition fails.
 83         .. versionchanged:: 0.10.1
 84             :class: save_condition failure now raises a `SaveConditionError`
 85         .. versionchanged:: 0.10.7
 86             Add signal_kwargs argument
 87         """
 88         if self._meta.get('abstract'):
 89             raise InvalidDocumentError('Cannot save an abstract document.')
 90 
 91         signal_kwargs = signal_kwargs or {}
 92         signals.pre_save.send(self.__class__, document=self, **signal_kwargs)
 93 
 94         if validate:
 95             self.validate(clean=clean)
 96 
 97         if write_concern is None:
 98             write_concern = {'w': 1}
 99 
100         doc = self.to_mongo()
101 
102         created = ('_id' not in doc or self._created or force_insert)
103 
104         signals.pre_save_post_validation.send(self.__class__, document=self,
105                                               created=created, **signal_kwargs)
106 
107         if self._meta.get('auto_create_index', True):
108             self.ensure_indexes()
109 
110         try:
111             # Save a new document or update an existing one
112             if created:
113                 object_id = self._save_create(doc, force_insert, write_concern)
114             else:
115                 object_id, created = self._save_update(doc, save_condition,
116                                                        write_concern)
117 
118             if cascade is None:
119                 cascade = (self._meta.get('cascade', False) or
120                            cascade_kwargs is not None)
121 
122             if cascade:
123                 kwargs = {
124                     'force_insert': force_insert,
125                     'validate': validate,
126                     'write_concern': write_concern,
127                     'cascade': cascade
128                 }
129                 if cascade_kwargs:  # Allow granular control over cascades
130                     kwargs.update(cascade_kwargs)
131                 kwargs['_refs'] = _refs
132                 self.cascade_save(**kwargs)
133 
134         except pymongo.errors.DuplicateKeyError as err:
135             message = u'Tried to save duplicate unique keys (%s)'
136             raise NotUniqueError(message % six.text_type(err))
137         except pymongo.errors.OperationFailure as err:
138             message = 'Could not save document (%s)'
139             if re.match('^E1100[01] duplicate key', six.text_type(err)):
140                 # E11000 - duplicate key error index
141                 # E11001 - duplicate key on update
142                 message = u'Tried to save duplicate unique keys (%s)'
143                 raise NotUniqueError(message % six.text_type(err))
144             raise OperationError(message % six.text_type(err))
145 
146         # Make sure we store the PK on this document now that it's saved
147         id_field = self._meta['id_field']
148         if created or id_field not in self._meta.get('shard_key', []):
149             self[id_field] = self._fields[id_field].to_python(object_id)
150         if not created:  # add by xiaogeho, 仅仅在数据更新时记录
151             signal_kwargs['update_doc'] = self._get_update_doc()  # signal_kwargs中增加修改的字段和值的信息
152         signals.post_save.send(self.__class__, document=self,
153                                created=created, **signal_kwargs)
154 
155         self._clear_changed_fields()
156         self._created = False
157         return self

save方法中,第150、151行为修改过的代码,其余为原本代码

 

2、 还需要一个接受信号的方法:

 1 def makeLogCallBack(sender, document, **kwargs):
 2     update_doc = kwargs.get('update_doc')
 3     oper_user = kwargs.get('oper_user', '')
 4     oper_name = kwargs.get('oper_name', '')
 5 
 6     if update_doc:
 7         print update_doc
 8         for k in update_doc:  # 格式化update_doc
 9             if type(update_doc[k]) == dict:
10                 update_doc_k = update_doc[k]
11                 for k2 in update_doc_k:
12                     if type(update_doc_k[k2]) == datetime.datetime:
13                         try:
14                             update_doc_k[k2] = update_doc_k[k2].strftime('%Y:%m:%d %H:%M:%S')
15                         except:
16                             update_doc_k[k2] = str(update_doc_k[k2])
17                     else:
18                         update_doc_k[k2] = str(update_doc_k[k2])
19             else:
20                 update_doc[k] = str(update_doc[k])
21         context = json.dumps(update_doc)
22         oper_log = OperLog()
23         oper_log.oper_user = oper_user
24         oper_log.oper_name = oper_name
25         oper_log.context = context
26         oper_log.save()

3、将信号附加到回调方法(makeLogCallBack)

在app/__init__.py中写入(也可以根据实际需要写到其他位置)

1 from mongoengine import signals
2 from .models import Sheet
3 from .utils import makeLogCallBack
4 signals.post_save.connect(makeLogCallBack, sender=Sheet)

4、数据修改后调用save方法,并触发信号

1 obj = Sheet.objects().first()
2 obj.lastpaytime = '2020-12-14 12:45:23'  # 最后一次支付请求时间
3 obj.paidtotime = '2020-12-14 12:45:23'  # 付款给司机时间
4 obj.oilpaidtime = '2020-12-14 12:33:21'  # 付中石化油款给司机时间
5 signal_msgs = {'oper_user':'admin', 'oper_name': '记录付款时间'}
6 obj.save(signal_kwargs=signal_msgs)  # signal_kwarg中的参数会传给信号回调函数makeLogCallBack

以上代码仅仅适用于使用save方法修改数据的场景;如果使用update方法修改数据,则需要修改update方法

参考: 官方文档

posted @ 2020-11-09 16:41  小哥豪  阅读(238)  评论(0编辑  收藏  举报