Merge pull request 'user_action_log_introduction' (#2) from user_action_log_introduction into master

Reviewed-on: SGD-Nord/konova#2
This commit is contained in:
Michel Peltriaux 2021-07-30 12:22:02 +02:00
commit b7ab9f6f55
14 changed files with 224 additions and 73 deletions

View File

@ -18,6 +18,24 @@ class CompensationTableFilter(InterventionTableFilter):
""" """
def _filter_show_all(self, queryset, name, value) -> QuerySet:
""" Filters queryset depending on value of 'show_all' setting
Args:
queryset ():
name ():
value ():
Returns:
"""
if not value:
return queryset.filter(
intervention__users__in=[self.user], # requesting user has access
)
else:
return queryset
def _filter_show_recorded(self, queryset, name, value) -> QuerySet: def _filter_show_recorded(self, queryset, name, value) -> QuerySet:
""" Filters queryset depending on value of 'show_recorded' setting """ Filters queryset depending on value of 'show_recorded' setting
@ -31,7 +49,7 @@ class CompensationTableFilter(InterventionTableFilter):
""" """
if not value: if not value:
return queryset.filter( return queryset.filter(
intervention__recorded_on=None, intervention__recorded=None,
) )
else: else:
return queryset return queryset

View File

@ -12,8 +12,8 @@ from django.utils import timezone
from django.utils.timezone import now from django.utils.timezone import now
from compensation.settings import COMPENSATION_IDENTIFIER_LENGTH, COMPENSATION_IDENTIFIER_TEMPLATE from compensation.settings import COMPENSATION_IDENTIFIER_LENGTH, COMPENSATION_IDENTIFIER_TEMPLATE
from intervention.models import Intervention from intervention.models import Intervention, ResponsibilityData
from konova.models import BaseObject, BaseResource, Geometry from konova.models import BaseObject, BaseResource, Geometry, UuidModel
from konova.utils.generators import generate_random_string from konova.utils.generators import generate_random_string
from organisation.models import Organisation from organisation.models import Organisation
@ -73,18 +73,19 @@ class Compensation(BaseObject):
The compensation holds information about which actions have to be performed until which date, who is in charge The compensation holds information about which actions have to be performed until which date, who is in charge
of this, which legal authority is the point of contact, and so on. of this, which legal authority is the point of contact, and so on.
""" """
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, related_name="+") responsible = models.OneToOneField(
conservation_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, related_name="+") ResponsibilityData,
on_delete=models.SET_NULL,
null=True,
blank=True,
help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle') and handler",
)
ground_definitions = models.CharField(max_length=500, null=True, blank=True) # ToDo: Need to be M2M to laws! before_states = models.ManyToManyField(CompensationState, blank=True, related_name='+', help_text="Refers to 'Ausgangszustand Biotop'")
action_definitions = models.CharField(max_length=500, null=True, blank=True) # ToDo: Need to be M2M to laws! after_states = models.ManyToManyField(CompensationState, blank=True, related_name='+', help_text="Refers to 'Zielzustand Biotop'")
actions = models.ManyToManyField(CompensationAction, help_text="Refers to 'Maßnahmen'")
before_states = models.ManyToManyField(CompensationState, blank=True, related_name='+') deadlines = models.ManyToManyField("konova.Deadline", null=True, blank=True, related_name="+")
after_states = models.ManyToManyField(CompensationState, blank=True, related_name='+')
actions = models.ManyToManyField(CompensationAction)
deadline_creation = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True, related_name="+")
deadline_maintaining = models.ForeignKey("konova.Deadline", on_delete=models.SET_NULL, null=True, blank=True, related_name="+")
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL) geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
documents = models.ManyToManyField("konova.Document", blank=True) documents = models.ManyToManyField("konova.Document", blank=True)
@ -100,9 +101,6 @@ class Compensation(BaseObject):
related_name='compensations' related_name='compensations'
) )
# Users having access on this object
users = models.ManyToManyField(User)
@staticmethod @staticmethod
def _generate_new_identifier() -> str: def _generate_new_identifier() -> str:
""" Generates a new identifier for the intervention object """ Generates a new identifier for the intervention object
@ -152,4 +150,5 @@ class EcoAccount(Compensation):
An eco account is a kind of 'prepaid' compensation. It can be compared to an account that already has been filled An eco account is a kind of 'prepaid' compensation. It can be compared to an account that already has been filled
with some kind of currency. From this account one is able to 'withdraw' currency for current projects. with some kind of currency. From this account one is able to 'withdraw' currency for current projects.
""" """
handler = models.CharField(max_length=500, null=True, blank=True, help_text="Who is responsible for handling the actions") # Users having access on this object
users = models.ManyToManyField(User)

View File

@ -34,13 +34,13 @@ class CompensationTable(BaseTable):
verbose_name=_("Checked"), verbose_name=_("Checked"),
orderable=True, orderable=True,
empty_values=[], empty_values=[],
accessor="intervention__checked_on", accessor="intervention__checked",
) )
r = tables.Column( r = tables.Column(
verbose_name=_("Recorded"), verbose_name=_("Recorded"),
orderable=True, orderable=True,
empty_values=[], empty_values=[],
accessor="intervention__recorded_on", accessor="intervention__recorded",
) )
e = tables.Column( e = tables.Column(
verbose_name=_("Editable"), verbose_name=_("Editable"),
@ -90,7 +90,7 @@ class CompensationTable(BaseTable):
def render_c(self, value, record: Compensation): def render_c(self, value, record: Compensation):
""" Renders the checked column for a compensation """ Renders the checked column for a compensation
checked_on is set by the main object Intervention checked is set by the main object Intervention
Args: Args:
value (str): The identifier value value (str): The identifier value
@ -103,9 +103,10 @@ class CompensationTable(BaseTable):
checked = value is not None checked = value is not None
tooltip = _("Not checked yet") tooltip = _("Not checked yet")
if checked: if checked:
value = value.timestamp
value = localtime(value) value = localtime(value)
checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT) checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
tooltip = _("Checked on {} by {}").format(checked_on, record.intervention.checked_by) tooltip = _("Checked on {} by {}").format(checked_on, record.intervention.checked.user)
html += self.render_checked_star( html += self.render_checked_star(
tooltip=tooltip, tooltip=tooltip,
icn_filled=checked, icn_filled=checked,
@ -126,9 +127,10 @@ class CompensationTable(BaseTable):
checked = value is not None checked = value is not None
tooltip = _("Not registered yet") tooltip = _("Not registered yet")
if checked: if checked:
value = value.timestamp
value = localtime(value) value = localtime(value)
on = value.strftime(DEFAULT_DATE_TIME_FORMAT) on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
tooltip = _("Registered on {} by {}").format(on, record.intervention.recorded_by) tooltip = _("Registered on {} by {}").format(on, record.intervention.recorded.user)
html += self.render_bookmark( html += self.render_bookmark(
tooltip=tooltip, tooltip=tooltip,
icn_filled=checked, icn_filled=checked,

View File

@ -1,17 +1,38 @@
from django.contrib import admin from django.contrib import admin
from intervention.models import Intervention from intervention.models import Intervention, ResponsibilityData, LegalData
class InterventionAdmin(admin.ModelAdmin): class InterventionAdmin(admin.ModelAdmin):
list_display = [ list_display = [
"id", "id",
"title", "title",
"process_type",
"handler",
"created_on", "created_on",
"deleted_on", "deleted_on",
] ]
class ResponsibilityAdmin(admin.ModelAdmin):
list_display = [
"id",
"registration_office",
"registration_file_number",
"conservation_office",
"conservation_file_number",
"handler",
]
class LegalAdmin(admin.ModelAdmin):
list_display = [
"id",
"process_type",
"law",
"registration_date",
"binding_date",
]
admin.site.register(Intervention, InterventionAdmin) admin.site.register(Intervention, InterventionAdmin)
admin.site.register(ResponsibilityData, ResponsibilityAdmin)
admin.site.register(LegalData, LegalAdmin)

View File

@ -117,7 +117,7 @@ class InterventionTableFilter(django_filters.FilterSet):
""" """
if not value: if not value:
return queryset.filter( return queryset.filter(
recorded_on=None, recorded=None,
) )
else: else:
return queryset return queryset

View File

@ -12,47 +12,84 @@ from django.utils import timezone
from django.utils.timezone import now from django.utils.timezone import now
from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE from intervention.settings import INTERVENTION_IDENTIFIER_LENGTH, INTERVENTION_IDENTIFIER_TEMPLATE
from konova.models import BaseObject, Geometry from konova.models import BaseObject, Geometry, UuidModel
from konova.utils.generators import generate_random_string from konova.utils.generators import generate_random_string
from organisation.models import Organisation from organisation.models import Organisation
from user.models import UserActionLogEntry
class ResponsibilityData(UuidModel):
"""
Holds intervention data about responsible organizations and their file numbers for this case
"""
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, related_name="+")
registration_file_number = models.CharField(max_length=1000, blank=True, null=True)
conservation_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, related_name="+")
conservation_file_number = models.CharField(max_length=1000, blank=True, null=True)
handler = models.CharField(max_length=500, null=True, blank=True, help_text="Refers to 'Eingriffsverursacher'")
class LegalData(UuidModel):
"""
Holds intervention legal data such as important dates, laws or responsible handler
"""
# Refers to "zugelassen am"
registration_date = models.DateField(null=True, blank=True, help_text="Refers to 'Zugelassen am'")
# Refers to "Bestandskraft am"
binding_date = models.DateField(null=True, blank=True, help_text="Refers to 'Bestandskraft am'")
process_type = models.CharField(max_length=500, null=True, blank=True)
law = models.CharField(max_length=500, null=True, blank=True)
class Intervention(BaseObject): class Intervention(BaseObject):
""" """
Interventions are e.g. construction sites where nature used to be. Interventions are e.g. construction sites where nature used to be.
""" """
registration_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, related_name="+") responsible = models.OneToOneField(
registration_file_number = models.CharField(max_length=1000, blank=True, null=True) ResponsibilityData,
conservation_office = models.ForeignKey(Organisation, on_delete=models.SET_NULL, null=True, related_name="+") on_delete=models.SET_NULL,
conservation_file_number = models.CharField(max_length=1000, blank=True, null=True) null=True,
blank=True,
process_type = models.CharField(max_length=500, null=True, blank=True) related_name='+',
law = models.CharField(max_length=500, null=True, blank=True) help_text="Holds data on responsible organizations ('Zulassungsbehörde', 'Eintragungsstelle')"
handler = models.CharField(max_length=500, null=True, blank=True, help_text="Who is responsible for this intervention?") )
legal = models.OneToOneField(
LegalData,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='+',
help_text="Holds data on legal dates or law"
)
geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL) geometry = models.ForeignKey(Geometry, null=True, blank=True, on_delete=models.SET_NULL)
documents = models.ManyToManyField("konova.Document", blank=True) documents = models.ManyToManyField("konova.Document", blank=True)
# Checks # Checks - Refers to "Genehmigen" but optional
checked_on = models.DateTimeField(default=None, null=True, blank=True) checked = models.OneToOneField(
checked_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name='+') UserActionLogEntry,
on_delete=models.SET_NULL,
# Refers to "zugelassen am" null=True,
registration_date = models.DateField(null=True, blank=True) blank=True,
help_text="Holds data on user and timestamp of this action",
# Refers to "Bestandskraft am" related_name="+"
binding_on = models.DateField(null=True, blank=True) )
# Refers to "verzeichnen" # Refers to "verzeichnen"
recorded_on = models.DateTimeField(default=None, null=True, blank=True) recorded = models.OneToOneField(
recorded_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL, related_name='+') UserActionLogEntry,
on_delete=models.SET_NULL,
null=True,
blank=True,
help_text="Holds data on user and timestamp of this action",
related_name="+"
)
# Holds which intervention is simply a newer version of this dataset # Holds which intervention is simply a newer version of this dataset
next_version = models.ForeignKey("Intervention", null=True, blank=True, on_delete=models.DO_NOTHING) next_version = models.ForeignKey("Intervention", null=True, blank=True, on_delete=models.DO_NOTHING)
# Compensation or payments, one-directional
#payments = models.ManyToManyField(Payment, related_name="+", blank=True)
#compensations = models.ManyToManyField(Compensation, related_name="+", blank=True)
# Users having access on this object # Users having access on this object
users = models.ManyToManyField(User) users = models.ManyToManyField(User)

View File

@ -33,13 +33,13 @@ class InterventionTable(BaseTable):
verbose_name=_("Checked"), verbose_name=_("Checked"),
orderable=True, orderable=True,
empty_values=[], empty_values=[],
accessor="checked_on", accessor="checked",
) )
r = tables.Column( r = tables.Column(
verbose_name=_("Recorded"), verbose_name=_("Recorded"),
orderable=True, orderable=True,
empty_values=[], empty_values=[],
accessor="recorded_on", accessor="recorded",
) )
e = tables.Column( e = tables.Column(
verbose_name=_("Editable"), verbose_name=_("Editable"),
@ -110,9 +110,10 @@ class InterventionTable(BaseTable):
checked = value is not None checked = value is not None
tooltip = _("Not checked yet") tooltip = _("Not checked yet")
if checked: if checked:
value = value.timestamp
value = localtime(value) value = localtime(value)
checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT) checked_on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
tooltip = _("Checked on {} by {}").format(checked_on, record.checked_by) tooltip = _("Checked on {} by {}").format(checked_on, record.checked.user)
html += self.render_checked_star( html += self.render_checked_star(
tooltip=tooltip, tooltip=tooltip,
icn_filled=checked, icn_filled=checked,
@ -133,9 +134,10 @@ class InterventionTable(BaseTable):
checked = value is not None checked = value is not None
tooltip = _("Not registered yet") tooltip = _("Not registered yet")
if checked: if checked:
value = value.timestamp
value = localtime(value) value = localtime(value)
on = value.strftime(DEFAULT_DATE_TIME_FORMAT) on = value.strftime(DEFAULT_DATE_TIME_FORMAT)
tooltip = _("Registered on {} by {}").format(on, record.recorded_by) tooltip = _("Registered on {} by {}").format(on, record.recorded.user)
html += self.render_bookmark( html += self.render_bookmark(
tooltip=tooltip, tooltip=tooltip,
icn_filled=checked, icn_filled=checked,

View File

@ -64,41 +64,41 @@
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Process type' %}</th> <th scope="row">{% trans 'Process type' %}</th>
<td class="align-middle">{{intervention.process_type|default_if_none:""}}</td> <td class="align-middle">{{intervention.legal.process_type|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Law' %}</th> <th scope="row">{% trans 'Law' %}</th>
<td class="align-middle">{{intervention.law|default_if_none:""}}</td> <td class="align-middle">{{intervention.legal.law|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Registration office' %}</th> <th scope="row">{% trans 'Registration office' %}</th>
<td class="align-middle">{{intervention.registration_office|default_if_none:""}}</td> <td class="align-middle">{{intervention.responsible.registration_office|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Registration office file number' %}</th> <th scope="row">{% trans 'Registration office file number' %}</th>
<td class="align-middle">{{intervention.registration_file_number|default_if_none:""}}</td> <td class="align-middle">{{intervention.responsible.registration_file_number|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Conservation office' %}</th> <th scope="row">{% trans 'Conservation office' %}</th>
<td class="align-middle">{{intervention.conservation_office|default_if_none:""}}</td> <td class="align-middle">{{intervention.responsible.conservation_office|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Conversation office file number' %}</th> <th scope="row">{% trans 'Conversation office file number' %}</th>
<td class="align-middle">{{intervention.conservation_file_number|default_if_none:""}}</td> <td class="align-middle">{{intervention.responsible.conservation_file_number|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Intervention handler' %}</th> <th scope="row">{% trans 'Intervention handler' %}</th>
<td class="align-middle">{{intervention.handler|default_if_none:""}}</td> <td class="align-middle">{{intervention.responsible.handler|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Checked' %}</th> <th scope="row">{% trans 'Checked' %}</th>
<td class="align-middle"> <td class="align-middle">
{% if intervention.checked_on is None %} {% if intervention.checked is None %}
<span> <span>
{% fa5_icon 'star' 'far' %} {% fa5_icon 'star' 'far' %}
</span> </span>
{% else %} {% else %}
<span class="check-star"> <span class="check-star" title="{% trans 'Checked on '%} {{intervention.checked.timestamp}} {% trans 'by' %} {{intervention.checked.user}}">
{% fa5_icon 'star' %} {% fa5_icon 'star' %}
</span> </span>
{% endif %} {% endif %}
@ -107,12 +107,12 @@
<tr> <tr>
<th scope="row">{% trans 'Recorded' %}</th> <th scope="row">{% trans 'Recorded' %}</th>
<td class="align-middle"> <td class="align-middle">
{% if intervention.recorded_on is None %} {% if intervention.recorded is None %}
<span> <span>
{% fa5_icon 'bookmark' 'far' %} {% fa5_icon 'bookmark' 'far' %}
</span> </span>
{% else %} {% else %}
<span class="registered-bookmark"> <span class="registered-bookmark" title="{% trans 'Recorded on '%} {{intervention.recorded.timestamp}} {% trans 'by' %} {{intervention.recorded.user}}">
{% fa5_icon 'bookmark' %} {% fa5_icon 'bookmark' %}
</span> </span>
{% endif %} {% endif %}
@ -120,11 +120,11 @@
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Registration date' %}</th> <th scope="row">{% trans 'Registration date' %}</th>
<td class="align-middle">{{intervention.registration_date|default_if_none:""}}</td> <td class="align-middle">{{intervention.legal.registration_date|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Binding on' %}</th> <th scope="row">{% trans 'Binding on' %}</th>
<td class="align-middle">{{intervention.binding_on|default_if_none:""}}</td> <td class="align-middle">{{intervention.legal.binding_on|default_if_none:""}}</td>
</tr> </tr>
<tr> <tr>
<th scope="row">{% trans 'Last modified' %}</th> <th scope="row">{% trans 'Last modified' %}</th>

View File

@ -7,7 +7,7 @@ Created on: 22.07.21
""" """
from django.contrib import admin from django.contrib import admin
from konova.models import Geometry, Document from konova.models import Geometry, Document, Deadline
class GeometryAdmin(admin.ModelAdmin): class GeometryAdmin(admin.ModelAdmin):
@ -28,5 +28,15 @@ class DocumentAdmin(admin.ModelAdmin):
] ]
class DeadlineAdmin(admin.ModelAdmin):
list_display = [
"id",
"type",
"date",
"comment",
]
admin.site.register(Geometry, GeometryAdmin) admin.site.register(Geometry, GeometryAdmin)
admin.site.register(Document, DocumentAdmin) admin.site.register(Document, DocumentAdmin)
admin.site.register(Deadline, DeadlineAdmin)

View File

@ -47,3 +47,17 @@ class ServerMessageImportance(BaseEnum):
DEFAULT = "DEFAULT" DEFAULT = "DEFAULT"
INFO = "INFO" INFO = "INFO"
WARNING = "WARNING" WARNING = "WARNING"
class UserActionLogEntryEnum(BaseEnum):
"""
Defines different possible user actions for UserActionLogEntry
"""
CHECKED = "Checked"
RECORDED = "Recorded"
class DeadlineTypeEnum(BaseEnum):
MAINTAIN = "Maintain"
CONTROL = "Control"
OTHER = "Other"

View File

@ -12,17 +12,27 @@ from django.contrib.auth.models import User
from django.contrib.gis.db.models import MultiPolygonField from django.contrib.gis.db.models import MultiPolygonField
from django.db import models from django.db import models
from konova.enums import DeadlineTypeEnum
from konova.settings import DEFAULT_SRID from konova.settings import DEFAULT_SRID
class BaseResource(models.Model): class UuidModel(models.Model):
""" """
A basic resource model, which defines attributes for every derived model Encapsules identifying via uuid
""" """
id = models.UUIDField( id = models.UUIDField(
primary_key=True, primary_key=True,
default=uuid.uuid4, default=uuid.uuid4,
) )
class Meta:
abstract = True
class BaseResource(UuidModel):
"""
A basic resource model, which defines attributes for every derived model
"""
created_on = models.DateTimeField(auto_now_add=True, null=True) created_on = models.DateTimeField(auto_now_add=True, null=True)
created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, related_name="+") created_by = models.ForeignKey(User, null=True, on_delete=models.SET_NULL, related_name="+")
@ -50,8 +60,9 @@ class Deadline(BaseResource):
""" """
Defines a deadline, which can be used to define dates with a semantic meaning Defines a deadline, which can be used to define dates with a semantic meaning
""" """
type = models.CharField(max_length=500, null=True, blank=True) type = models.CharField(max_length=255, null=True, blank=True, choices=DeadlineTypeEnum.as_choices(drop_empty_choice=True))
date = models.DateField(null=True, blank=True) date = models.DateField(null=True, blank=True)
comment = models.CharField(max_length=1000, null=True, blank=True)
def __str__(self): def __str__(self):
return self.type return self.type

View File

@ -78,7 +78,7 @@ def home_view(request: HttpRequest):
next_version=None, next_version=None,
) )
user_comps = comps.filter( user_comps = comps.filter(
users__in=[user] intervention__users__in=[user]
) )
eco_accs = EcoAccount.objects.filter( eco_accs = EcoAccount.objects.filter(
deleted_on=None, deleted_on=None,

View File

@ -1,6 +1,6 @@
from django.contrib import admin from django.contrib import admin
from user.models import UserNotification, KonovaUserExtension from user.models import UserNotification, KonovaUserExtension, UserActionLogEntry
class UserNotificationAdmin(admin.ModelAdmin): class UserNotificationAdmin(admin.ModelAdmin):
@ -17,5 +17,15 @@ class KonovaUserExtensionAdmin(admin.ModelAdmin):
] ]
class UserActionLogEntryAdmin(admin.ModelAdmin):
list_display = [
"id",
"user",
"timestamp",
"action",
]
admin.site.register(UserNotification, UserNotificationAdmin) admin.site.register(UserNotification, UserNotificationAdmin)
admin.site.register(KonovaUserExtension, KonovaUserExtensionAdmin) admin.site.register(KonovaUserExtension, KonovaUserExtensionAdmin)
admin.site.register(UserActionLogEntry, UserActionLogEntryAdmin)

View File

@ -1,6 +1,9 @@
import uuid
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from konova.enums import UserActionLogEntryEnum
from user.enums import UserNotificationEnum from user.enums import UserNotificationEnum
@ -36,3 +39,27 @@ class KonovaUserExtension(models.Model):
""" """
user = models.OneToOneField(User, on_delete=models.CASCADE) user = models.OneToOneField(User, on_delete=models.CASCADE)
notifications = models.ManyToManyField(UserNotification, related_name="+") notifications = models.ManyToManyField(UserNotification, related_name="+")
class UserActionLogEntry(models.Model):
""" Wraps a user action log entry
Can be used for workflow related attributes like checking or recording.
"""
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
)
user = models.ForeignKey(User, related_name='+', on_delete=models.CASCADE, help_text="Performing user")
timestamp = models.DateTimeField(auto_now_add=True, help_text="Timestamp of performed action")
action = models.CharField(
max_length=255,
null=True,
blank=True,
help_text="Short name for performed action - optional",
choices=UserActionLogEntryEnum.as_choices(drop_empty_choice=True),
)
def __str__(self):
return "{} | {} | {}".format(self.user.username, self.timestamp, self.action)