Как я должен структурировать объекты базы данных с приглашениями и получателями для веб-приложения электронной подписи в Django?

#django #django-models #database-design #django-rest-framework #relationship

Вопрос:

Я заинтересован в реализации следующих требований к моему веб-приложению для электронной подписи.

  1. Пользователь может создать новый контракт на подписание. Этот контракт может включать в себя подписание несколькими пользователями. Создателю контракта необходимо предоставить электронные письма получателей. Каждому получателю будут назначены дополнительные данные, такие как данные для подписи, инструкции и т. Д.
  2. Однако приглашенный пользователь все равно может отсутствовать в системе. Это самая сложная часть.

Прямо сейчас моя следующая реализация заключается в следующем:

  1. Я создаю контракт, затем проверяю, присутствует ли пользователь в системе, создав фильтр по электронной почте. Если пользователь существует, я создаю объект «многие ко многим» ContractRecipientEvent, используя промежуточную таблицу с дополнительными данными, которая назначается контракту. Я создаю его «многие ко многим», потому что один и тот же пользователь может быть назначен нескольким контрактам.
  2. Если пользователь отсутствует, я создаю модель приглашения, устанавливаю все конкретные данные получателя и отправляю электронное письмо. Затем пользователь регистрируется, я запускаю запрос записей приглашений ll с этим электронным письмом и создаю событие ContractRecipientEvent, скопировав данные из модели приглашения.

Что мне не нравится в моем подходе, так это следующие вещи:

  1. Поле «Многие ко многим». Я хотел бы просто использовать простые внешние ключи для получателей моего контракта, но я не уверен, как мне назначить нескольких пользователей для одного и того же контракта? Возможно, мне следует создать новую модель контракта, доступную пользователю, и заключить контракт в качестве внешних ключей, но это также поле «многие ко многим»?
  2. Мне не нравится, что мне нужно копировать данные из модели приглашения в событие contractrecipient и создавать событие contractrecipient только после регистрации пользователя, потому что мне нужна пользовательская сущность для создания события contractrecipient, у которого есть внешний ключ для пользователя.
  3. Структурой разрешений трудно управлять. Мне нужно проверить всех пользователей, которые включены в запись базы данных контрактов, и проверить, присвоен ли им идентификатор контракта, который они используют для запроса на подписание.

Я прилагаю свой окончательный код 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"
        }
      ]
    },
 
  ]
}
 

Комментарии:

1. Я не понимаю твоего отвращения ко многим людям. Не могли бы вы немного подробнее остановиться на этом? Вызывает ли это у вас проблемы и что можно было бы решить с помощью «простых внешних ключей»?

2. Здравствуйте, с тех пор я получил помощь от другого пользователя. Я воспользуюсь предложенным им подходом. Вы согласны с этим? Подход заключается в следующем: softwareengineering.stackexchange.com/a/423672/387517

3. Будет лучше, если вы поделитесь диаграммой ERD вашей базы данных или структурами ваших текущих моделей, это сделает ее более понятной с меньшим количеством слов и сохранит описание только для описания вашего системного потока

4. Да, я согласен с частью M2M. Это обычный шаблон в моделях данных, не зависящий от языка программирования или фреймворка. Это также не «простые внешние ключи», а два внешних ключа. OneToOneField-это специальный внешний ключ (с уникальным ограничением). О пользователе, однако, можно реализовать гостевого пользователя и создать сеанс для этого пользователя. Вы не храните ничего постоянного, если пользователь не укажет, что он хочет зарегистрироваться. В конце концов, подписание документа не должно за кулисами регистрировать человека в вашей службе.

Ответ №1:

Ну, единственная разница между M2M и двумя внешними ключами-это сквозная таблица, но давайте посмотрим, понимаю ли я. Что, если бы мы начали со следующих моделей:

 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

Вот один из примеров ограничения запросов к Signature таблице на основе объектов, использующих DRF:

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)
 

Комментарии:

1. Спасибо. Как я должен управлять незарегистрированными пользователями? Должен ли я создавать их только по электронной почте, как только владелец контракта добавит их в качестве получателей, и когда они решат зарегистрироваться, я введу пароль и другие данные в модель?

2. Да, точно — вы можете использовать get_or_create , чтобы получить или создать пользователя на основе его электронной почты, — затем вы можете добавить немного дополнительной логики для новых пользователей. Вы также можете инициализировать «временный» пароль и потребовать, чтобы новые пользователи меняли свои пароли при первой регистрации и т.д. — См. Мои изменения

3. Существует несколько способов решения этой проблемы — вероятно, лучший способ — использовать разрешения на уровне объектов — т. Е. Только пользователи, у которых есть подписи в контракте, могут отправлять запрос на публикацию «подписать» — я добавлю пример, используя DRF, как это может выглядеть.

4. Похоже на правду — договорная модель будет иметь отношения foreignkey для пользователя owner (1 собственник согласно контракту, многие из которых принадлежат договоров на одного пользователя) и М2М отношению к signers (несколько подписывающих лиц за контракт и несколько контрактов, подписанных на одного пользователя) — тогда, как вы говорите, вам может понадобиться, чтобы переместить is_signed поле Event , которое является истинным, когда signer подписывает контракт.

5. Пользователь может вызвать множество событий (подписано много контрактов), контракт может иметь несколько событий (многие люди подписали контракт), подпись может быть в состоянии обойтись без O2O, если когда-либо будет только одно событие на подпись (однако я предположил, что существует несколько шагов для «подписания» контракта, следовательно, FK)