Skip to content

Commit

Permalink
Merge pull request #279 from City-of-Helsinki/develop
Browse files Browse the repository at this point in the history
Merge develop to master
  • Loading branch information
ehaivala authored Sep 25, 2020
2 parents a04b425 + 7674ab9 commit 07fa774
Show file tree
Hide file tree
Showing 16 changed files with 487 additions and 65 deletions.
37 changes: 20 additions & 17 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,28 @@
#
# pip-compile dev-requirements.in
#
attrs==19.3.0 # via pytest
click==7.1.1 # via pip-tools
entrypoints==0.3 # via flake8
flake8==3.7.9 # via -r dev-requirements.in
freezegun==0.3.15 # via -r dev-requirements.in
importlib-metadata==1.6.0 # via pluggy, pytest
isort==4.3.21 # via -r dev-requirements.in
attrs==20.2.0 # via pytest
click==7.1.2 # via pip-tools
flake8==3.8.3 # via -r dev-requirements.in
freezegun==1.0.0 # via -r dev-requirements.in
importlib-metadata==1.7.0 # via flake8, pluggy, pytest
iniconfig==1.0.1 # via pytest
isort==5.5.3 # via -r dev-requirements.in
mccabe==0.6.1 # via flake8
more-itertools==8.2.0 # via pytest
packaging==20.3 # via pytest
pip-tools==4.5.1 # via -r dev-requirements.in
more-itertools==8.5.0 # via pytest
packaging==20.4 # via pytest
pip-tools==5.3.1 # via -r dev-requirements.in
pluggy==0.13.1 # via pytest
py==1.8.1 # via pytest
pycodestyle==2.5.0 # via flake8
pyflakes==2.1.1 # via flake8
py==1.9.0 # via pytest
pycodestyle==2.6.0 # via flake8
pyflakes==2.2.0 # via flake8
pyparsing==2.4.7 # via packaging
pytest-django==3.9.0 # via -r dev-requirements.in
pytest==5.4.1 # via -r dev-requirements.in, pytest-django
pytest-django==3.10.0 # via -r dev-requirements.in
pytest==6.0.2 # via -r dev-requirements.in, pytest-django
python-dateutil==2.8.1 # via freezegun
six==1.14.0 # via freezegun, packaging, pip-tools, python-dateutil
wcwidth==0.1.9 # via pytest
six==1.15.0 # via packaging, pip-tools, python-dateutil
toml==0.10.1 # via pytest
zipp==3.1.0 # via importlib-metadata

# The following packages are considered to be unsafe in a requirements file:
# pip
7 changes: 6 additions & 1 deletion metarecord/admin/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ class MetadataVersionInline(admin.TabularInline):

model = MetadataVersion
extra = 0
readonly_fields = ('modified_at', 'modified_by', 'state', 'valid_from', 'valid_to')
readonly_fields = ('modified_at', 'get_modified_by', 'state', 'valid_from', 'valid_to')
exclude = ('modified_by', '_modified_by')

def get_modified_by(self, obj):
return obj.get_modified_by_display()
get_modified_by.short_description = _('modified by')

def has_add_permission(self, request, obj=None):
return False
Expand Down
73 changes: 73 additions & 0 deletions metarecord/migrations/0044_user_relation_texts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Generated by Django 2.2.12 on 2020-04-28 10:28

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('metarecord', '0043_add_name_and_help_text_to_attribute_value'),
]

operations = [
migrations.AddField(
model_name='action',
name='_created_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='created by (text)'),
),
migrations.AddField(
model_name='action',
name='_modified_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='modified by (text)'),
),
migrations.AddField(
model_name='bulkupdate',
name='_approved_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='approved by (text)'),
),
migrations.AddField(
model_name='bulkupdate',
name='_created_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='created by (text)'),
),
migrations.AddField(
model_name='bulkupdate',
name='_modified_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='modified by (text)'),
),
migrations.AddField(
model_name='function',
name='_created_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='created by (text)'),
),
migrations.AddField(
model_name='function',
name='_modified_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='modified by (text)'),
),
migrations.AddField(
model_name='metadataversion',
name='_modified_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='modified by (text)'),
),
migrations.AddField(
model_name='phase',
name='_created_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='created by (text)'),
),
migrations.AddField(
model_name='phase',
name='_modified_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='modified by (text)'),
),
migrations.AddField(
model_name='record',
name='_created_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='created by (text)'),
),
migrations.AddField(
model_name='record',
name='_modified_by',
field=models.CharField(blank=True, editable=False, max_length=200, verbose_name='modified by (text)'),
),
]
79 changes: 79 additions & 0 deletions metarecord/migrations/0045_populate_persistent_user_name_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Generated by Django 2.2.12 on 2020-04-30 08:11

from django.db import migrations
from django.db.models import OuterRef, Subquery, Value as V
from django.db.models.functions import Coalesce, Concat, Trim


def populate_user_name_field(apps, field_name, model_class):
"""
Populate related user's full name to equivalent char field prefixed with
single underscore. The char field is expected to be in the same table as
the foreign key.
"""
User = apps.get_model('users', 'User')
char_field_name = '_%s' % field_name

full_name = (
User.objects
.filter(pk=OuterRef(field_name))
.annotate(full_name=Trim(Concat('first_name', V(' '), 'last_name')))
.values_list('full_name')[:1]
)

model_class.objects.exclude(
**{field_name: None}
).update(
**{char_field_name: Subquery(full_name)}
)


def populate_function_user_name_fields(apps, schema_editor):
Function = apps.get_model('metarecord', 'Function')
populate_user_name_field(apps, 'created_by', Function)
populate_user_name_field(apps, 'modified_by', Function)


def populate_phase_user_name_fields(apps, schema_editor):
Phase = apps.get_model('metarecord', 'Phase')
populate_user_name_field(apps, 'created_by', Phase)
populate_user_name_field(apps, 'modified_by', Phase)


def populate_action_user_name_fields(apps, schema_editor):
Action = apps.get_model('metarecord', 'Action')
populate_user_name_field(apps, 'created_by', Action)
populate_user_name_field(apps, 'modified_by', Action)


def populate_record_user_name_fields(apps, schema_editor):
Record = apps.get_model('metarecord', 'Record')
populate_user_name_field(apps, 'created_by', Record)
populate_user_name_field(apps, 'modified_by', Record)


def populate_bulk_update_user_name_fields(apps, schema_editor):
BulkUpdate = apps.get_model('metarecord', 'BulkUpdate')
populate_user_name_field(apps, 'approved_by', BulkUpdate)
populate_user_name_field(apps, 'created_by', BulkUpdate)
populate_user_name_field(apps, 'modified_by', BulkUpdate)


def populate_metadata_version_user_name_fields(apps, schema_editor):
MetadataVersion = apps.get_model('metarecord', 'MetadataVersion')
populate_user_name_field(apps, 'modified_by', MetadataVersion)


class Migration(migrations.Migration):
dependencies = [
('metarecord', '0044_user_relation_texts'),
]

operations = [
migrations.RunPython(populate_function_user_name_fields, migrations.RunPython.noop),
migrations.RunPython(populate_phase_user_name_fields, migrations.RunPython.noop),
migrations.RunPython(populate_action_user_name_fields, migrations.RunPython.noop),
migrations.RunPython(populate_record_user_name_fields, migrations.RunPython.noop),
migrations.RunPython(populate_bulk_update_user_name_fields, migrations.RunPython.noop),
migrations.RunPython(populate_metadata_version_user_name_fields, migrations.RunPython.noop),
]
17 changes: 16 additions & 1 deletion metarecord/models/bulk_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class BulkUpdate(TimeStampedModel, UUIDPrimaryKeyModel):
editable=False,
on_delete=models.SET_NULL
)
_created_by = models.CharField(verbose_name=_('created by (text)'), max_length=200, blank=True, editable=False)
_modified_by = models.CharField(verbose_name=_('modified by (text)'), max_length=200, blank=True, editable=False)
_approved_by = models.CharField(verbose_name=_('approved by (text)'), max_length=200, blank=True, editable=False)

is_approved = models.BooleanField(verbose_name=_('is approved'), default=False)
changes = JSONField(verbose_name=_('changes'), blank=True, default=dict)
Expand Down Expand Up @@ -81,7 +84,8 @@ def approve(self, user):
self.apply_changes(user)
self.is_approved = True
self.approved_by = user
self.save(update_fields=['is_approved', 'approved_by'])
self._approved_by = user.get_full_name()
self.save(update_fields=['is_approved', 'approved_by', '_approved_by'])

@transaction.atomic
def apply_changes(self, user):
Expand Down Expand Up @@ -128,3 +132,14 @@ def apply_changes(self, user):
record = action_records.get(uuid=record_uuid)
self._apply_changes_to_instance(record, record_updates, fields=('attributes',))
record.save()

def save(self, *args, **kwargs):
# Only update `_created_by` and `_modified_by` value if the relations
# are set set. Text values should persist even if related user is deleted.
if self.created_by:
self._created_by = self.created_by.get_full_name()

if self.modified_by:
self._modified_by = self.modified_by.get_full_name()

super().save(*args, **kwargs)
12 changes: 12 additions & 0 deletions metarecord/models/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class MetadataVersion(models.Model):
modified_by = models.ForeignKey(
settings.AUTH_USER_MODEL, verbose_name=_('modified by'), blank=True, null=True, on_delete=models.SET_NULL
)
_modified_by = models.CharField(verbose_name=_('modified by (text)'), max_length=200, blank=True, editable=False)
state = models.CharField(
verbose_name=_('state'), max_length=20, choices=Function.STATE_CHOICES, default=Function.DRAFT
)
Expand All @@ -205,3 +206,14 @@ class Meta:

def __str__(self):
return '' # because of admin UI

def get_modified_by_display(self):
return self._modified_by

def save(self, *args, **kwargs):
# Only update _modified_by value if modified_by is set.
# _modified_by should persist even if related user is deleted.
if self.modified_by:
self._modified_by = self.modified_by.get_full_name()

super().save(*args, **kwargs)
17 changes: 13 additions & 4 deletions metarecord/models/structural_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class StructuralElement(TimeStampedModel):
modified_by = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('modified by'),
null=True, blank=True, related_name='%(class)s_modified', editable=False,
on_delete=models.SET_NULL)
_created_by = models.CharField(verbose_name=_('created by (text)'), max_length=200, blank=True, editable=False)
_modified_by = models.CharField(verbose_name=_('modified by (text)'), max_length=200, blank=True, editable=False)
index = models.PositiveSmallIntegerField(null=True, editable=False, db_index=True)
attributes = JSONField(verbose_name=_('attributes'), blank=True, default=dict)

Expand Down Expand Up @@ -96,15 +98,22 @@ def can_view_modified_by(cls, user):
return user.has_perm('metarecord.can_view_modified_by')

def get_modified_by_display(self):
if self.modified_by:
return '{} {}'.format(self.modified_by.first_name, self.modified_by.last_name).strip()
return None
return self._modified_by or None

def save(self, *args, **kwargs):
# Only update `_created_by` and `_modified_by` value if the relations
# are set set. Text values should persist even if related user is deleted.
if self.created_by:
self._created_by = self.created_by.get_full_name()

if self.modified_by:
self._modified_by = self.modified_by.get_full_name()

for key, value in self.attributes.copy().items():
if value in ('', None):
del self.attributes[key]
return super().save(*args, **kwargs)

super().save(*args, **kwargs)


def _get_conditionally_required_schema(required_attributes, condition_attribute, condition_values):
Expand Down
Loading

0 comments on commit 07fa774

Please sign in to comment.