#django #django-models #database-design #django-rest-framework #relationship
Вопрос:
Я заинтересован в реализации следующих требований к моему веб-приложению для электронной подписи.
- Пользователь может создать новый контракт на подписание. Этот контракт может включать в себя подписание несколькими пользователями. Создателю контракта необходимо предоставить электронные письма получателей. Каждому получателю будут назначены дополнительные данные, такие как данные для подписи, инструкции и т. Д.
- Однако приглашенный пользователь все равно может отсутствовать в системе. Это самая сложная часть.
Прямо сейчас моя следующая реализация заключается в следующем:
- Я создаю контракт, затем проверяю, присутствует ли пользователь в системе, создав фильтр по электронной почте. Если пользователь существует, я создаю объект «многие ко многим» ContractRecipientEvent, используя промежуточную таблицу с дополнительными данными, которая назначается контракту. Я создаю его «многие ко многим», потому что один и тот же пользователь может быть назначен нескольким контрактам.
- Если пользователь отсутствует, я создаю модель приглашения, устанавливаю все конкретные данные получателя и отправляю электронное письмо. Затем пользователь регистрируется, я запускаю запрос записей приглашений ll с этим электронным письмом и создаю событие ContractRecipientEvent, скопировав данные из модели приглашения.
Что мне не нравится в моем подходе, так это следующие вещи:
- Поле «Многие ко многим». Я хотел бы просто использовать простые внешние ключи для получателей моего контракта, но я не уверен, как мне назначить нескольких пользователей для одного и того же контракта? Возможно, мне следует создать новую модель контракта, доступную пользователю, и заключить контракт в качестве внешних ключей, но это также поле «многие ко многим»?
- Мне не нравится, что мне нужно копировать данные из модели приглашения в событие contractrecipient и создавать событие contractrecipient только после регистрации пользователя, потому что мне нужна пользовательская сущность для создания события contractrecipient, у которого есть внешний ключ для пользователя.
- Структурой разрешений трудно управлять. Мне нужно проверить всех пользователей, которые включены в запись базы данных контрактов, и проверить, присвоен ли им идентификатор контракта, который они используют для запроса на подписание.
Я прилагаю свой окончательный код 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)