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方法
参考: 官方文档