我应该如何使用 Django 中的电子签名 Web 应用程序的邀请和收件人构建我的数据库实体?

How should I structure my database entities with invites and recipients for e-signing web app in Django?

我有兴趣为我的电子签名网络应用程序实现以下要求。

  1. 用户可以创建新的签约合同。该合同可以包括多个用户签署。合约创建者需要提供收件人的电子邮件。每个收件人都会分配额外的数据,例如签名详细信息、说明等。
  2. 但是,受邀用户仍然可以不在系统中。这是最棘手的部分。

现在我的以下实现如下:

  1. 我创建了一个合同,然后通过电子邮件筛选来检查用户是否存在于系统中。如果用户存在,我创建一个 多对多实体 ContractRecipientEvent 使用 through intermediate table 附加data,分配给合约。我创建它是多对多的,因为同一个用户可以分配给多个合同。
  2. 如果用户不在,我创建邀请模型,设置所有收件人的特定数据,然后发送电子邮件。然后用户注册,我 运行 查询 带有该电子邮件 的所有邀请记录并通过 复制 数据创建 ContractRecipientEvent邀请模式。

我不喜欢我的方法有以下几点:

  1. 多对多字段。我只想为我的合同接收者使用纯外键,但我不确定我应该如何将多个用户分配给同一个合同?也许我应该创建一个新的模型 ContractRecipient,将用户和合同作为外键,但这也是一个多对多字段?
  2. 我不喜欢我需要将数据从 Invitation 模型复制到 ContractRecipientEvent 并且只在用户注册后创建 ContractRecipientEvent,因为我需要一个用户实体来创建一个 ContractRecipientEvent,它有一个用户的外键。
  3. 权限结构难以管理。我需要检查合同数据库记录中包含的所有用户,并检查他们是否已分配给 合同 ID,他们用于签署 POST 请求 .

我附上合同清单的最终 JSON 代码。它有效,但我想要一个正确的模型结构:

{
  "results": [
    {
      "id": 178,
      "is_author": true,
      "title": "ahhzhzh",
      "message_to_all_recipients": null,
      "contract_signing_status": "WAITING_FOR_ME",
      "contract_signing_type": "SIMPLE",
      "contract_signing_date": {
        "start_date": "2010-09-04T14:15:22Z",
        "end_date": "2010-09-04T14:15:22Z"
      },
      "recipients": [
        {
          "message": null,
          "recipient_signing_status": "NOT_SIGNED",
          "recipient_review_status": "NOT_REQUIRED",
          "recipient_action": "SIGN",
          "role": "ADMIN",
          "email": "test2331@gmail.com"
        },
        {
          "message": null,
          "recipient_signing_status": "NOT_SIGNED",
          "recipient_review_status": "NOT_REQUIRED",
          "recipient_action": "SIGN",
          "role": "BASE",
          "email": "test2333@gmail.com"
        }
      ]
    },
    {
      "id": 179,
      "is_author": true,
      "title": "dhhdhd",
      "message_to_all_recipients": null,
      "contract_signing_status": "WAITING_FOR_ME",
      "contract_signing_type": "SIMPLE",
      "contract_signing_date": {
        "start_date": "2010-09-04T14:15:22Z",
        "end_date": "2010-09-04T14:15:22Z"
      },
      "recipients": [
        {
          "message": null,
          "recipient_signing_status": "NOT_SIGNED",
          "recipient_review_status": "NOT_REQUIRED",
          "recipient_action": "SIGN",
          "role": "ADMIN",
          "email": "test123@gmail.com"
        },
        {
          "message": null,
          "recipient_signing_status": "NOT_SIGNED",
          "recipient_review_status": "NOT_REQUIRED",
          "recipient_action": "SIGN",
          "role": "BASE",
          "email": "test233@gmail.com"
        }
      ]
    },
 
  ]
}

M2M 和两个 ForeignKeys 之间的唯一区别是通过 table 但让我们看看我是否理解。如果我们从以下模型开始会怎样:

class User(models.Model):
  email = models.CharField(...)
  ...

class Contract(models.Model):
  user = models.ForeignKey('User', ..., related_name='contracts')
  ...

class Signature(models.Model):
  user = models.ForeignKey('User', ..., related_name='signatures')
  contract = models.ForeignKey('User', ..., related_name='signatures')
  is_signed = models.BooleanField(default=False)
  ...

class Event(models.Model):
  user = models.ForeignKey('User', ..., related_name='events')
  contract = models.ForeignKey('Contract', ..., related_name='events')
  signature = models.ForeignKey('Signature', ..., related_name='events') 
  message = models.CharField(...)
  ...  

现在我们可以做如下事情:

# get a specific user:
user = User.objects.get(email=<email>)

# get all of the contracts they own:
users_contracts = user.contracts.all() # OR
users_contracts = Contract.objects.filter(user=user)

# get a specific contract:
contract = Contract.objects.get(id=<contract-id>)

# get all the signatures on a contract:
signatures_on_contract = contract.signatures.all() # OR
signatures_on_contract = Signature.objects.filter(contract=contract)

# get all the signatures for a user:
users_signatures = user.signatures.all()

# get all the contracts that the user signed:
users_signed_contracts = Contracts.objects.filter(
  signatures__in = users_signatures,
  signatures__is_signed = True
)

# get all the events on the contract:
events = contract.events.order_by('id')

现在我们的合同 json 可以看起来像:

// i.e.: contract with id 7:
{
  'id' : 7,
  'user' : {
    'id' : 2,
    'email' : 'some@email.com'
  },
  'signatures' : [
    {
      'id' : 3,
      'user' : {
        'id' : 2,
        'email' : 'some@email.com'
      },
      'is_signed' : true
    },
    {
      'user' : {
        'id' : 4,
        'email' : 'other@email.com'
      },
      'is_signed' : false
    }
  ],
  'events' : [
    {
      'id' : 6,
      'user' : {
        'id' : 2,
        'email' : 'some@email.com'
      },
      'contract' : {
        'id' : 7
      },
      'signature' : {
        'id' : 3
      },
      'message' : 'signed contract 7'
    }
  ]
}

此处的 Event 模型可能过于合格,不需要它拥有的所有 ForeignKey 关系,但这样您可以灵活地创建您的 json.

编辑

处理需要签约的用户:

# a list of emails:
emails = ['email@1.com', 'email@2.com', ...]

for email in emails:

    # get or create a user:
    user, created = User.objects.get_or_create(email=email)

    # new user logic:
    if created:
        # set temp password
        # redirect users to change password page before signing a doc (can be done elsewhere)
        ...

    # existing user logic:
    else:
        ...

    # create signatures for each user, and add them to the contract:
    signature = Signature.objects.create(user=user, contract=contract)
    ...

编辑 2

这是一个使用 DRF 基于对象限制对 Signature table 的请求的示例:

views.py

class SignatureViewSet(viewsets.ModelViewSet):

    # override this method to limit access:
    def get_queryset(self):

        # superusers can access all items:
        if self.request.user.is_superuser:
            return self.queryset

        # otherwise, users can only access their own signatures:
        else:
            return self.queryset.filter(user=self.request.user)