资讯专栏INFORMATION COLUMN

在 Odoo 中生成唯一不重复的序列号

wujl596 / 3243人阅读

摘要:最近在做的项目中有一个需求是要让某个字段值根据记录产生的日期和一定的组合规则按顺序生成一个序列号,这个序列号不可重复,这原本是一个很常见的需求,没有多想就写好了。

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

经过讨论和思考后有几种解决方案,一是在数据库表层加锁,一是采用类似 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并添加以下代码:



  
    
      Demo Sequence SN
      demo_sequence.sequence
      SN%(y)s%(month)s%(day)s
      3
    
  

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

name - 名字,随便叫什么都行

code - 调用生成编码的 Key,需保证唯一性

prefix - 前缀,可以是固定的字面量也可以是组合参数

padding - 序列递增的位数

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

接下来就是调用得到按规则生成的序列号,同样重写模型的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 中 查看并下载 。


注:本文转自我的个人博客「TNK」,发布于 2017-12-08,通过原文地址可以访问到我的博客原文。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41401.html

相关文章

  • Odoo 基础教程系列」第二篇——从 Todo 应用开始(1)

    摘要:虽然这是个很简单的应用,但是希望大家可以动手一起操作,从最简单的开始上手学习如何使用这个框架。则是在和之间,负责响应用户操作,从中获取数据进行处理并返回到中。 showImg(https://segmentfault.com/img/bV66tE?w=728&h=410); 在第一篇教程发布之后差不多一个月的今天,终于完成了第二篇内容,这个发布周期拖得实在是有点太长了,我都觉得不好意思...

    UCloud 评论0 收藏0
  • Odoo 基础教程系列」第一篇——环境准备

    摘要:安装好后,在中执行查看版本信息,应该会看到输出如下信息版本号可能会不同如果提示未找到,则需要手动将用户基础目录下的添加到中。相关文章基础教程系列第篇开天坑啦 showImg(https://segmentfault.com/img/bV4GZu?w=1262&h=911); 之前说好的 「Odoo 基础教程系列」终于来了(撒花)~刚过完年重新投入到工作中,一下子事情有点多都要忙不过来了...

    szysky 评论0 收藏0
  • odoo基础数据加载

    摘要:内部就是定义具体记录的列名和值,可以有多个列,如下数据文件需在或字段里列出,才能在模块安装更新后正确的加载数据只在勾选演示数据后才会加载数据在系统启动后会自动进行加载 odoo 基础数据加载 这里介绍的odoo基础数据加载分两种方式,一种是演示数据加载,一种是默认数据加载,下面就是详细介绍 首先,当然是创建一个date文件夹 项目目录,右键自定义一个文件夹 XML数据定义格式 ...

    airborne007 评论0 收藏0
  • odoo基础数据加载

    摘要:内部就是定义具体记录的列名和值,可以有多个列,如下数据文件需在或字段里列出,才能在模块安装更新后正确的加载数据只在勾选演示数据后才会加载数据在系统启动后会自动进行加载 odoo 基础数据加载 这里介绍的odoo基础数据加载分两种方式,一种是演示数据加载,一种是默认数据加载,下面就是详细介绍 首先,当然是创建一个date文件夹 项目目录,右键自定义一个文件夹 XML数据定义格式 ...

    Labradors 评论0 收藏0
  • odoo基础数据加载

    摘要:内部就是定义具体记录的列名和值,可以有多个列,如下数据文件需在或字段里列出,才能在模块安装更新后正确的加载数据只在勾选演示数据后才会加载数据在系统启动后会自动进行加载 odoo 基础数据加载 这里介绍的odoo基础数据加载分两种方式,一种是演示数据加载,一种是默认数据加载,下面就是详细介绍 首先,当然是创建一个date文件夹 项目目录,右键自定义一个文件夹 XML数据定义格式 ...

    rollback 评论0 收藏0

发表评论

0条评论

wujl596

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<