Odoo中如何生成唯一不重复的序列号详解


Posted in Python onFebruary 10, 2018

前言

最近在做的项目中有一个需求是要让某个字段值根据记录产生的日期和一定的组合规则按顺序生成一个序列号,这个序列号不可重复,这原本是一个很常见的需求,没有多想就写好了。由于没有考虑到并发的情况,到后面测试的时候才发现一个比较严重的问题,如果用户同时操作产生的记录,生成的序列号会出现重复。

经过讨论和思考后有几种解决方案,一是在数据库表层加锁,一是采用类似 redis 的消息队列,还有就是通过文件锁达到数据库排他锁的目的,鉴于时间和项目当前的情况,最后采用了通过文件锁实现这个需求。

其实除了以上几种方式,Odoo 本身就有一个模型(ir.sequence)是用于生成序列的,可以很方便地实现这个需求,因为之前一直没有接触过这个模块,还是在项目之后的阶段同事使用到了并且告诉我之后才知道原来有这么个好东西的存在。在这里我将会把我原本通过文件锁实现的方式和通过 Odoo 自带的ir.sequence实现的方式都记录下来。

给文件加锁 - fcntl

fcntl是 Python 标准库里的一个模块,用来对文件进行加锁的操作。在实现中主要用到的是下面这个函数:

def flock(fd, operation):
 """
 flock(fd, operation)

 Perform the lock operation op on file descriptor fd. See the Unix 
 manual page for flock(2) for details. (On some systems, this function is
 emulated using fcntl().)
 """
 pass

其中fd是文件描述符,operation为锁的操作,总共有4种:

  • fcntl.LOCK_EX - 排他锁
  • fcntl.LOCK_NB - 非阻塞锁
  • fcntl.LOCK_SH - 共享锁
  • fcntl.LOCK_UN - 解锁

关于fcntl的其他具体内容请查看 官方标准库文档 。

下面来看一下具体的实现,在给出代码之前,先描述一下需求,假设模型中有一个字段sn用于存储按一定规则生成的序列号,序列号的组成规则如下:

  • 固定的前缀SN
  • 取记录生成的日期组成的6位数字%y%m%d,如2017年12月8日取值为171208
  • 最后是3位的流水号,从001开始递增
  • 生成的序列号不能有重复
  • 最后的3位流水号每天自动重置,从001开始递增(这个需求涉及到一些扩展,故此文将不实现这一需求)

需求很简单,也很清楚了,下面就上代码开始具体的实现。首先创建一个模块demo_sequence:

./odoo-bin scaffold demo_sequence

然后在模块的目录下创建数据文件目录data/,在目录下创建一个data.xml文件,在后面会用到;继续在模块目录下创建静态文件目录static/,在目录下创建一个空文件SN.LOCK用作加锁的文件对象。完成之后的目录结构如下:

demo_sequence
├── __init__.py
├── __manifest__.py
├── controllers
│ ├── __init__.py
│ └── controllers.py
├── data
│ └── data.xml
├── demo
│ └── demo.xml
├── models
│ ├── __init__.py
│ └── models.py
├── security
│ └── ir.model.access.csv
├── static
│ └── SN.LOCK
└── views
├── templates.xml
└── views.xml

模型的创建和视图的编写,这里将跳过不说,具体的代码将在后面给出。先创建一个给文件加锁的函数:

def _file_lock(flag=fcntl.LOCK_EX):
 FILE_PATH = get_module_resource('demo_sequence', 'static/SN.LOCK')
 file = open(FILE_PATH)
 fcntl.flock(file.fileno(), flag)
 _logger.info('Acquire Lock')
 return file

然后重写模型的create()方法:

@api.model
def create(self, vals):
 file = _file_lock()
 sn_prefix = 'SN' + datetime.date.today().strftime("%y%m%d")
 obj = self.env['demo_sequence.fcntl'].search_read([('sn', '=like', sn_prefix + '%')], limit=1, order='sn DESC')
 # 今天已经有序列号,在最新的序列号上递增
 if obj and obj[0]['sn'].startswith(sn_prefix):
 sn_suffix = int(obj[0]['sn'][-3:]) + 1
 vals['sn'] = sn_prefix + str(sn_suffix).zfill(3) # 补0
 else:
 vals['sn'] = sn_prefix + '001'
 res = super(DemoSequence, self).create(vals)
 # 关闭文件将自动解锁
 file.close()
 return res

利用fcntl给文件加锁后再生成序列号写入数据库,以达到序列号不重复的目的,就这么点代码就搞定了,不过还有更简单的方式,就是利用 Odoo 自带的ir.sequence模型产生序列号。

生成唯一标识 - ir.sequence

在模型ir.sequence中是这样描述的:

The sequence model allows to define and use so-called sequence objects. Such objects are used to generate unique identifiers in a transaction-safeway.

我们可以利用它生成唯一的标识,下面就看一下怎么用ir.sequence实现前面所说的需求。

打开data/data.xml并添加以下代码:

<?xml version="1.0" encoding="utf-8"?>
<odoo>
 <data noupdate="1">
 <record id="seq_demo_sequence_sn" model="ir.sequence">
  <field name="name">Demo Sequence SN</field>
  <field name="code">demo_sequence.sequence</field>
  <field name="prefix">SN%(y)s%(month)s%(day)s</field>
  <field name="padding">3</field>
 </record>
 </data>
</odoo>

这里的参数实际上不止这几个,在实现需求的前提下这几个就够用了,分别说明一下各个参数的作用:

  • name - 名字,随便叫什么都行
  • code - 调用生成编码的 Key,需保证唯一性
  • prefix - 前缀,可以是固定的字面量也可以是组合参数
  • padding - 序列递增的位数

注:记得将data/data.xml加入到__manifest__.py的data列表中

接下来就是调用得到按规则生成的序列号,同样重写模型的create()方法:

@api.model
def create(self, vals):
 vals['sn'] = self.env['ir.sequence'].next_by_code('demo_sequence.sequence')
 return super(DemoSequence2, self).create(vals)

可以看到只需要一行代码就可以得到一个唯一的序列号,比前面用fcntl给文件加锁的方式简单了几个级别。这里的调用就用到了前面定义中所写的code。

在实际项目中所使用到的两种方式都已经在这里记录下来了,官方的东西确实是个好东西,回头看看自己写的东西,毕竟 too young,可惜官方的文档好像并没有相关的记录(抑或是我没找到?),多翻翻官方实现的功能模块源码,才是精进 Odoo 之道。

源码下载

以上出现的所有代码均可在仓库 ruter/TNK-Odoo-Demo 中 查看并下载 (大家也可以通过本地下载)。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
编写Python脚本抓取网络小说来制作自己的阅读器
Aug 20 Python
浅谈python 里面的单下划线与双下划线的区别
Dec 01 Python
python psutil库安装教程
Mar 19 Python
python控制windows剪贴板,向剪贴板中写入图片的实例
May 31 Python
使用Python对微信好友进行数据分析
Jun 27 Python
python 读取目录下csv文件并绘制曲线v111的方法
Jul 06 Python
Python爬虫实现获取动态gif格式搞笑图片的方法示例
Dec 24 Python
利用python脚本如何简化jar操作命令
Feb 24 Python
python进程池实现的多进程文件夹copy器完整示例
Nov 27 Python
Python使用eval函数执行动态标表达式过程详解
Oct 17 Python
pymongo insert_many 批量插入的实例
Dec 05 Python
python状态机transitions库详解
Jun 02 Python
python TCP Socket的粘包和分包的处理详解
Feb 09 #Python
python实现Adapter模式实例代码
Feb 09 #Python
python实现Decorator模式实例代码
Feb 09 #Python
Python多线程扫描端口代码示例
Feb 09 #Python
Python编程实现从字典中提取子集的方法分析
Feb 09 #Python
python tensorflow学习之识别单张图片的实现的示例
Feb 09 #Python
python删除服务器文件代码示例
Feb 09 #Python
You might like
PHP 增加了对 .ZIP 文件的读取功能
2006/10/09 PHP
php 获取本地IP代码
2013/06/23 PHP
PHP永久登录、记住我功能实现方法和安全做法
2015/04/27 PHP
CI框架常用函数封装实例
2016/11/21 PHP
php使用自定义函数实现汉字分割替换功能示例
2017/01/30 PHP
php下载远程大文件(获取远程文件大小)的实例
2017/06/17 PHP
php基于Redis消息队列实现的消息推送的方法
2018/11/28 PHP
PHP常量define和const的区别详解
2019/05/18 PHP
统一接口:为FireFox添加IE的方法和属性的js代码
2007/03/25 Javascript
javascript FormatNumber函数实现方法
2008/12/30 Javascript
Javascript图像处理—为矩阵添加常用方法
2012/12/27 Javascript
jquery实现的可隐藏重现的靠边悬浮层实例代码
2013/05/27 Javascript
js切换光标示例代码
2013/10/10 Javascript
jquery实现点击消失的代码
2014/03/03 Javascript
javascript中对Date类型的常用操作小结
2016/05/19 Javascript
js弹出框、对话框、提示框、弹窗实现方法总结(推荐)
2016/05/31 Javascript
AngularJS基础 ng-click 指令示例代码
2016/08/01 Javascript
angularjs实现过滤并替换关键字小功能
2017/09/19 Javascript
旺旺在线客服代码 旺旺客服代码生成器
2018/01/09 Javascript
js中DOM事件绑定分析
2018/03/18 Javascript
实例讲解vue源码架构
2019/01/24 Javascript
bootstrap table实现横向合并与纵向合并
2019/07/18 Javascript
js中的面向对象之对象常见创建方法详解
2019/12/16 Javascript
AI小程序之语音听写来了,十分钟掌握百度大脑语音听写全攻略
2020/03/13 Javascript
vue解决跨域问题(推荐)
2020/11/10 Javascript
python使用paramiko模块实现ssh远程登陆上传文件并执行
2014/01/27 Python
PyQt中使用QtSql连接MySql数据库的方法
2020/07/28 Python
python em算法的实现
2020/10/03 Python
Skyscanner波兰:廉价航班
2017/11/07 全球购物
老板电器官方购物商城:老板油烟机、燃气灶、消毒柜、电烤箱
2018/05/30 全球购物
求职者简历中的自我评价
2013/10/20 职场文书
助人为乐表扬信范文
2014/01/14 职场文书
预防职务犯罪警示教育心得体会
2016/01/15 职场文书
python内置进制转换函数的操作
2021/06/02 Python
php访问对象中的成员的实例方法
2021/11/17 PHP
win10更新失败无限重启解决方法
2022/04/19 数码科技