2021-07-01 13:36:07 +02:00
|
|
|
"""
|
|
|
|
Author: Michel Peltriaux
|
|
|
|
Organization: Struktur- und Genehmigungsdirektion Nord, Rhineland-Palatinate, Germany
|
|
|
|
Contact: michel.peltriaux@sgdnord.rlp.de
|
2021-11-15 17:41:52 +01:00
|
|
|
Created on: 15.11.21
|
2021-07-01 13:36:07 +02:00
|
|
|
|
|
|
|
"""
|
2021-11-15 17:41:52 +01:00
|
|
|
|
2021-07-01 13:36:07 +02:00
|
|
|
import uuid
|
|
|
|
|
2021-08-05 12:54:28 +02:00
|
|
|
from django.contrib.auth.models import User
|
2021-09-23 15:05:17 +02:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
|
|
from django.utils import timezone
|
2021-08-11 14:31:24 +02:00
|
|
|
from django.utils.timezone import now
|
2021-08-04 15:19:06 +02:00
|
|
|
from django.db import models, transaction
|
2021-08-11 14:31:24 +02:00
|
|
|
from compensation.settings import COMPENSATION_IDENTIFIER_TEMPLATE, COMPENSATION_IDENTIFIER_LENGTH, \
|
|
|
|
ECO_ACCOUNT_IDENTIFIER_TEMPLATE, ECO_ACCOUNT_IDENTIFIER_LENGTH
|
2021-08-19 13:02:31 +02:00
|
|
|
from ema.settings import EMA_ACCOUNT_IDENTIFIER_LENGTH, EMA_ACCOUNT_IDENTIFIER_TEMPLATE
|
2021-08-11 14:31:24 +02:00
|
|
|
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
|
2021-10-26 15:09:30 +02:00
|
|
|
from konova.utils import generators
|
2021-08-11 14:31:24 +02:00
|
|
|
from konova.utils.generators import generate_random_string
|
2021-08-04 15:19:06 +02:00
|
|
|
from user.models import UserActionLogEntry, UserAction
|
2021-07-22 13:19:14 +02:00
|
|
|
|
2021-07-01 13:36:07 +02:00
|
|
|
|
2021-07-30 09:30:33 +02:00
|
|
|
class UuidModel(models.Model):
|
2021-07-01 13:36:07 +02:00
|
|
|
"""
|
2021-07-30 09:30:33 +02:00
|
|
|
Encapsules identifying via uuid
|
2021-07-01 13:36:07 +02:00
|
|
|
"""
|
|
|
|
id = models.UUIDField(
|
|
|
|
primary_key=True,
|
|
|
|
default=uuid.uuid4,
|
2021-08-03 13:13:01 +02:00
|
|
|
editable=False,
|
2021-07-01 13:36:07 +02:00
|
|
|
)
|
2021-07-30 09:30:33 +02:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
|
|
|
|
|
|
|
|
class BaseResource(UuidModel):
|
|
|
|
"""
|
|
|
|
A basic resource model, which defines attributes for every derived model
|
|
|
|
"""
|
2021-08-19 13:02:31 +02:00
|
|
|
created = models.ForeignKey(
|
|
|
|
UserActionLogEntry,
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
related_name='+'
|
|
|
|
)
|
|
|
|
modified = models.ForeignKey(
|
|
|
|
UserActionLogEntry,
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
related_name='+',
|
|
|
|
help_text="Last modified"
|
|
|
|
)
|
2021-07-01 13:36:07 +02:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
|
2021-08-04 15:19:06 +02:00
|
|
|
def delete(self, using=None, keep_parents=False):
|
2021-09-29 14:49:17 +02:00
|
|
|
""" Base deleting of a resource
|
2021-09-23 15:05:17 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
using ():
|
|
|
|
keep_parents ():
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
try:
|
2021-08-04 15:19:06 +02:00
|
|
|
self.created.delete()
|
2021-11-16 08:29:18 +01:00
|
|
|
except (ObjectDoesNotExist, AttributeError):
|
2021-09-23 15:05:17 +02:00
|
|
|
# Object does not exist anymore - we can skip this
|
|
|
|
pass
|
2021-08-04 15:19:06 +02:00
|
|
|
super().delete()
|
|
|
|
|
2021-07-01 13:36:07 +02:00
|
|
|
|
|
|
|
class BaseObject(BaseResource):
|
|
|
|
"""
|
|
|
|
A basic object model, which specifies BaseResource.
|
|
|
|
|
|
|
|
Mainly used for intervention, compensation, ecoaccount
|
|
|
|
"""
|
|
|
|
identifier = models.CharField(max_length=1000, null=True, blank=True)
|
|
|
|
title = models.CharField(max_length=1000, null=True, blank=True)
|
2021-08-02 11:52:20 +02:00
|
|
|
deleted = models.ForeignKey(UserActionLogEntry, on_delete=models.SET_NULL, null=True, blank=True, related_name='+')
|
2021-07-21 15:40:34 +02:00
|
|
|
comment = models.TextField(null=True, blank=True)
|
2021-08-05 12:54:28 +02:00
|
|
|
log = models.ManyToManyField(UserActionLogEntry, blank=True, help_text="Keeps all user actions of an object", editable=False)
|
2021-07-01 13:36:07 +02:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
|
2021-09-23 15:05:17 +02:00
|
|
|
def mark_as_deleted(self, user: User):
|
|
|
|
""" Mark an entry as deleted
|
2021-08-04 15:19:06 +02:00
|
|
|
|
|
|
|
Does not delete from database but sets a timestamp for being deleted on and which user deleted the object
|
|
|
|
|
|
|
|
Args:
|
2021-09-23 15:05:17 +02:00
|
|
|
user (User): The performing user
|
2021-08-04 15:19:06 +02:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
if self.deleted:
|
|
|
|
# Nothing to do here
|
|
|
|
return
|
|
|
|
|
|
|
|
with transaction.atomic():
|
|
|
|
action = UserActionLogEntry.objects.create(
|
2021-09-23 15:05:17 +02:00
|
|
|
user=user,
|
|
|
|
action=UserAction.DELETED,
|
|
|
|
timestamp=timezone.now()
|
2021-08-04 15:19:06 +02:00
|
|
|
)
|
|
|
|
self.deleted = action
|
2021-09-23 15:05:17 +02:00
|
|
|
self.log.add(action)
|
2021-08-04 15:19:06 +02:00
|
|
|
self.save()
|
|
|
|
|
2021-08-05 12:54:28 +02:00
|
|
|
def add_log_entry(self, action: UserAction, user: User, comment: str):
|
2021-11-11 13:13:05 +01:00
|
|
|
""" Wraps adding of UserActionLogEntry to log
|
2021-08-05 12:54:28 +02:00
|
|
|
|
|
|
|
Args:
|
|
|
|
action (UserAction): The performed UserAction
|
|
|
|
user (User): Performing user
|
2021-11-11 13:13:05 +01:00
|
|
|
comment (str): The optional comment
|
2021-08-05 12:54:28 +02:00
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
user_action = UserActionLogEntry.objects.create(
|
|
|
|
user=user,
|
|
|
|
action=action,
|
|
|
|
comment=comment
|
|
|
|
)
|
|
|
|
self.log.add(user_action)
|
|
|
|
|
2021-10-05 16:35:24 +02:00
|
|
|
def generate_new_identifier(self) -> str:
|
2021-08-11 14:31:24 +02:00
|
|
|
""" Generates a new identifier for the intervention object
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
str
|
|
|
|
"""
|
|
|
|
from compensation.models import Compensation, EcoAccount
|
|
|
|
from intervention.models import Intervention
|
2021-08-19 13:02:31 +02:00
|
|
|
from ema.models import Ema
|
|
|
|
|
2021-08-11 14:31:24 +02:00
|
|
|
definitions = {
|
|
|
|
Intervention: {
|
|
|
|
"length": INTERVENTION_IDENTIFIER_LENGTH,
|
|
|
|
"template": INTERVENTION_IDENTIFIER_TEMPLATE,
|
|
|
|
},
|
|
|
|
Compensation: {
|
|
|
|
"length": COMPENSATION_IDENTIFIER_LENGTH,
|
|
|
|
"template": COMPENSATION_IDENTIFIER_TEMPLATE,
|
|
|
|
},
|
|
|
|
EcoAccount: {
|
|
|
|
"length": ECO_ACCOUNT_IDENTIFIER_LENGTH,
|
|
|
|
"template": ECO_ACCOUNT_IDENTIFIER_TEMPLATE,
|
|
|
|
},
|
2021-08-19 13:02:31 +02:00
|
|
|
Ema: {
|
|
|
|
"length": EMA_ACCOUNT_IDENTIFIER_LENGTH,
|
|
|
|
"template": EMA_ACCOUNT_IDENTIFIER_TEMPLATE,
|
|
|
|
},
|
2021-08-11 14:31:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if self.__class__ not in definitions:
|
|
|
|
# Not defined, yet. Create fallback identifier for this case
|
|
|
|
return generate_random_string(10)
|
|
|
|
|
|
|
|
_now = now()
|
|
|
|
curr_month = str(_now.month)
|
|
|
|
curr_year = str(_now.year)
|
|
|
|
rand_str = generate_random_string(
|
|
|
|
length=definitions[self.__class__]["length"],
|
2021-10-06 13:10:10 +02:00
|
|
|
use_numbers=True,
|
|
|
|
use_letters_lc=False,
|
|
|
|
use_letters_uc=True,
|
2021-08-11 14:31:24 +02:00
|
|
|
)
|
|
|
|
_str = "{}{}-{}".format(curr_month, curr_year, rand_str)
|
|
|
|
return definitions[self.__class__]["template"].format(_str)
|
|
|
|
|
2021-07-01 13:36:07 +02:00
|
|
|
|
2021-11-16 08:29:18 +01:00
|
|
|
class RecordableObjectMixin(models.Model):
|
2021-10-26 15:09:30 +02:00
|
|
|
""" Wraps record related fields and functionality
|
2021-10-25 17:01:02 +02:00
|
|
|
|
|
|
|
"""
|
2021-10-26 15:09:30 +02:00
|
|
|
# Refers to "verzeichnen"
|
|
|
|
recorded = models.OneToOneField(
|
|
|
|
UserActionLogEntry,
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
help_text="Holds data on user and timestamp of this action",
|
|
|
|
related_name="+"
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
|
2021-10-25 17:39:39 +02:00
|
|
|
def set_unrecorded(self, user: User):
|
|
|
|
""" Perform unrecording
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user (User): Performing user
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
action = UserActionLogEntry.objects.create(
|
|
|
|
user=user,
|
|
|
|
action=UserAction.UNRECORDED
|
|
|
|
)
|
|
|
|
self.recorded = None
|
|
|
|
self.save()
|
|
|
|
self.log.add(action)
|
2021-11-11 13:13:05 +01:00
|
|
|
return action
|
2021-10-25 17:39:39 +02:00
|
|
|
|
|
|
|
def set_recorded(self, user: User):
|
|
|
|
""" Perform recording
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user (User): Performing user
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
action = UserActionLogEntry.objects.create(
|
|
|
|
user=user,
|
|
|
|
action=UserAction.RECORDED
|
|
|
|
)
|
|
|
|
self.recorded = action
|
|
|
|
self.save()
|
|
|
|
self.log.add(action)
|
2021-11-11 13:13:05 +01:00
|
|
|
return action
|
2021-10-25 17:39:39 +02:00
|
|
|
|
2021-11-11 13:13:05 +01:00
|
|
|
def toggle_recorded(self, user: User) -> UserActionLogEntry:
|
2021-10-25 17:01:02 +02:00
|
|
|
""" Un/Record intervention
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user (User): Performing user
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
if not self.recorded:
|
2021-11-11 13:13:05 +01:00
|
|
|
ret_log_entry = self.set_recorded(user)
|
2021-10-25 17:01:02 +02:00
|
|
|
else:
|
2021-11-11 13:13:05 +01:00
|
|
|
ret_log_entry = self.set_unrecorded(user)
|
|
|
|
return ret_log_entry
|
2021-10-25 17:39:39 +02:00
|
|
|
|
|
|
|
|
2021-11-16 08:29:18 +01:00
|
|
|
class CheckableObjectMixin(models.Model):
|
2021-10-26 15:09:30 +02:00
|
|
|
# Checks - Refers to "Genehmigen" but optional
|
|
|
|
checked = models.OneToOneField(
|
|
|
|
UserActionLogEntry,
|
|
|
|
on_delete=models.SET_NULL,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
help_text="Holds data on user and timestamp of this action",
|
|
|
|
related_name="+"
|
|
|
|
)
|
2021-11-11 13:13:05 +01:00
|
|
|
|
2021-10-26 15:09:30 +02:00
|
|
|
class Meta:
|
|
|
|
abstract = True
|
2021-10-25 17:39:39 +02:00
|
|
|
|
2021-11-11 13:13:05 +01:00
|
|
|
def set_unchecked(self) -> None:
|
2021-10-25 17:39:39 +02:00
|
|
|
""" Perform unrecording
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
2021-11-11 13:13:05 +01:00
|
|
|
# Do not .delete() the checked attribute! Just set it to None, since a delete() would kill it out of the
|
|
|
|
# log history, which is not what we want!
|
2021-10-25 17:39:39 +02:00
|
|
|
self.checked = None
|
|
|
|
self.save()
|
2021-11-11 13:13:05 +01:00
|
|
|
return None
|
2021-10-25 17:39:39 +02:00
|
|
|
|
2021-11-11 13:13:05 +01:00
|
|
|
def set_checked(self, user: User) -> UserActionLogEntry:
|
2021-10-25 17:39:39 +02:00
|
|
|
""" Perform checking
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user (User): Performing user
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
action = UserActionLogEntry.objects.create(
|
|
|
|
user=user,
|
|
|
|
action=UserAction.CHECKED
|
|
|
|
)
|
|
|
|
self.checked = action
|
2021-10-25 17:01:02 +02:00
|
|
|
self.save()
|
|
|
|
self.log.add(action)
|
2021-11-11 13:13:05 +01:00
|
|
|
return action
|
2021-10-25 17:39:39 +02:00
|
|
|
|
2021-11-11 13:13:05 +01:00
|
|
|
def toggle_checked(self, user: User) -> UserActionLogEntry:
|
2021-10-25 17:39:39 +02:00
|
|
|
""" Un/Record intervention
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user (User): Performing user
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
if not self.checked:
|
2021-11-11 13:13:05 +01:00
|
|
|
ret_log_entry = self.set_checked(user)
|
2021-10-25 17:39:39 +02:00
|
|
|
else:
|
2021-11-11 13:13:05 +01:00
|
|
|
ret_log_entry = self.set_unchecked()
|
|
|
|
return ret_log_entry
|
2021-10-26 15:09:30 +02:00
|
|
|
|
|
|
|
|
2021-11-16 08:29:18 +01:00
|
|
|
class ShareableObjectMixin(models.Model):
|
2021-10-26 15:09:30 +02:00
|
|
|
# Users having access on this object
|
|
|
|
users = models.ManyToManyField(User, help_text="Users having access (data shared with)")
|
|
|
|
access_token = models.CharField(
|
|
|
|
max_length=255,
|
|
|
|
null=True,
|
|
|
|
blank=True,
|
|
|
|
help_text="Used for sharing access",
|
|
|
|
)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
abstract = True
|
|
|
|
|
|
|
|
def generate_access_token(self, make_unique: bool = False, rec_depth: int = 5):
|
|
|
|
""" Creates a new access token for the data
|
|
|
|
|
|
|
|
Tokens are not used for identification of a table row. The share logic checks the intervention id as well
|
|
|
|
as the given token. Therefore two different interventions can hold the same access_token without problems.
|
|
|
|
For (possible) future changes to the share logic, the make_unique parameter may be used for checking whether
|
|
|
|
the access_token is already used in any intervention. If so, tokens will be generated as long as a free token
|
|
|
|
can be found.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
make_unique (bool): Perform check on uniqueness over all intervention entries
|
|
|
|
rec_depth (int): How many tries for generating a free random token (only if make_unique)
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
# Make sure we won't end up in an infinite loop of trying to generate access_tokens
|
|
|
|
rec_depth = rec_depth - 1
|
|
|
|
if rec_depth < 0 and make_unique:
|
|
|
|
raise RuntimeError(
|
|
|
|
"Access token generating for {} does not seem to find a free random token! Aborted!".format(self.id)
|
|
|
|
)
|
|
|
|
|
|
|
|
# Create random token
|
|
|
|
token = generators.generate_random_string(15, True, True, False)
|
|
|
|
# Check dynamically wheter there is another instance of that model, which holds this random access token
|
|
|
|
_model = self._meta.concrete_model
|
|
|
|
token_used_in = _model.objects.filter(access_token=token)
|
|
|
|
# Make sure the token is not used anywhere as access_token, yet.
|
|
|
|
# Make use of QuerySet lazy method for checking if it exists or not.
|
|
|
|
if token_used_in and make_unique:
|
|
|
|
self.generate_access_token(make_unique, rec_depth)
|
|
|
|
else:
|
|
|
|
self.access_token = token
|
2021-11-16 12:51:10 +01:00
|
|
|
self.save()
|
|
|
|
|
|
|
|
def is_shared_with(self, user: User):
|
|
|
|
""" Access check
|
|
|
|
|
|
|
|
Checks whether a given user has access to this object
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user ():
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
return self.users.filter(id=user.id)
|
|
|
|
|
|
|
|
def share_with(self, user: User):
|
|
|
|
""" Adds user to list of shared access users
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user (User): The user to be added to the object
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
if not self.is_shared_with(user):
|
|
|
|
self.users.add(user)
|
|
|
|
|
|
|
|
def share_with_list(self, user_list: list):
|
|
|
|
""" Sets the list of shared access users
|
|
|
|
|
|
|
|
Args:
|
|
|
|
user_list (list): The users to be added to the object
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
self.users.set(user_list)
|
2021-11-16 12:54:28 +01:00
|
|
|
|
|
|
|
def update_sharing_user(self, form):
|
|
|
|
""" Adds a new user with shared access to the object
|
|
|
|
|
|
|
|
Args:
|
|
|
|
form (ShareModalForm): The form holding the data
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
|
|
"""
|
|
|
|
form_data = form.cleaned_data
|
|
|
|
|
|
|
|
keep_accessing_users = form_data["users"]
|
|
|
|
new_accessing_users = list(form_data["user_select"].values_list("id", flat=True))
|
|
|
|
accessing_users = keep_accessing_users + new_accessing_users
|
|
|
|
users = User.objects.filter(
|
|
|
|
id__in=accessing_users
|
|
|
|
)
|
|
|
|
self.share_with_list(users)
|