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

所属分类: 脚本专栏 / python 阅读数: 1350
收藏 0 赞 0 分享

前言

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

经过讨论和思考后有几种解决方案,一是在数据库表层加锁,一是采用类似 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实现按学生年龄排序实际问题的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面跟着小编来一起学习学习吧。
收藏 0 赞 0 分享

Python开发的HTTP库requests详解

Requests是用Python语言编写,基于urllib,采用Apache2 Licensed开源协议的HTTP库。它比urllib更加方便,可以节约我们大量的工作,完全满足HTTP测试需求。Requests的哲学是以PEP 20 的习语为中心开发的,所以它比urllib更加P
收藏 0 赞 0 分享

Python网络爬虫与信息提取(实例讲解)

下面小编就为大家带来一篇Python网络爬虫与信息提取(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

在python3环境下的Django中使用MySQL数据库的实例

下面小编就为大家带来一篇在python3环境下的Django中使用MySQL数据库的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
收藏 0 赞 0 分享

Python 3.x读写csv文件中数字的方法示例

在我们日常开发中经常需要对csv文件进行读写,下面这篇文章主要给大家介绍了关于Python 3.x读写csv文件中数字的相关资料,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面跟着小编来一起学习学习吧。
收藏 0 赞 0 分享

Python实现解析Bit Torrent种子文件内容的方法

这篇文章主要介绍了Python实现解析Bit Torrent种子文件内容的方法,结合实例形式分析了Python针对Torrent文件的读取与解析相关操作技巧与注意事项,需要的朋友可以参考下
收藏 0 赞 0 分享

Python实现文件内容批量追加的方法示例

这篇文章主要介绍了Python实现文件内容批量追加的方法,结合实例形式分析了Python文件的读写相关操作技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

Python简单实现自动删除目录下空文件夹的方法

这篇文章主要介绍了Python简单实现自动删除目录下空文件夹的方法,涉及Python针对文件与目录的读取、判断、删除等相关操作技巧,需要的朋友可以参考下
收藏 0 赞 0 分享

简单学习Python多进程Multiprocessing

这篇文章主要和大家一起简单的学习Python多进程Multiprocessing ,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
收藏 0 赞 0 分享

Python导入模块时遇到的错误分析

这篇文章主要给大家详细解释了在Python处理导入模块的时候出现错误以及具体的情况分析,非常的详尽,有需要的小伙伴可以参考下
收藏 0 赞 0 分享
查看更多