Skip to content
Snippets Groups Projects
Commit 445a83b5 authored by Piotr Gawron's avatar Piotr Gawron
Browse files

Merge branch 'feature/provenance' into 'master'

Feature/provenance

See merge request NCER-PD/scheduling-system!229
parents f6656bf1 e5933929
No related branches found
No related tags found
1 merge request!229Feature/provenance
Pipeline #24411 passed
Showing
with 346 additions and 11 deletions
pycurl==7.43.0.2
asn1crypto==0.24.0 asn1crypto==0.24.0
Babel==2.6.0 Babel==2.6.0
backports.functools-lru-cache==1.5 backports.functools-lru-cache==1.5
...@@ -36,7 +37,6 @@ phonenumberslite==8.9.14 ...@@ -36,7 +37,6 @@ phonenumberslite==8.9.14
Pillow==3.4.2 Pillow==3.4.2
psycopg2==2.7.6.1 psycopg2==2.7.6.1
pycparser==2.19 pycparser==2.19
pycurl==7.43.0.2
pyexcel==0.5.3 pyexcel==0.5.3
pyexcel-io==0.5.9.1 pyexcel-io==0.5.9.1
pyexcel-webio==0.1.4 pyexcel-webio==0.1.4
......
...@@ -2,11 +2,12 @@ import logging ...@@ -2,11 +2,12 @@ import logging
from collections import OrderedDict from collections import OrderedDict
from django import forms from django import forms
from django.db.models.query import QuerySet
from django.forms import ModelForm from django.forms import ModelForm
from web.models.worker_study_role import WORKER_STAFF
from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, APPOINTMENT_TYPES_FIELD_POSITION from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, APPOINTMENT_TYPES_FIELD_POSITION
from web.models import Appointment, Worker, AppointmentTypeLink, AppointmentType from web.models import Appointment, Worker, AppointmentTypeLink, AppointmentType, Provenance
from web.models.worker_study_role import WORKER_STAFF
from web.views.notifications import get_filter_locations from web.views.notifications import get_filter_locations
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -22,6 +23,47 @@ class AppointmentForm(ModelForm): ...@@ -22,6 +23,47 @@ class AppointmentForm(ModelForm):
self.fields['worker_assigned'].queryset = Worker.get_workers_by_worker_type(WORKER_STAFF).filter( self.fields['worker_assigned'].queryset = Worker.get_workers_by_worker_type(WORKER_STAFF).filter(
locations__in=get_filter_locations(self.user)).distinct().order_by('first_name', 'last_name') locations__in=get_filter_locations(self.user)).distinct().order_by('first_name', 'last_name')
def save_changes(self):
for change in self.changes:
if change.modified_table_id is None:
change.modified_table_id = self.instance.id
change.save()
def register_changes(self):
self.changes = []
for field in self.changed_data:
new_value = self.cleaned_data[field] or self.data[field]
if isinstance(new_value, QuerySet):
new_human_values = '; '.join([str(element) for element in new_value])
new_value = ','.join([str(element.id) for element in new_value]) #overwrite variable
#old value
if self.instance.id: #update instance
list_of_values = getattr(self.instance, field).all()
old_human_values = '; '.join([str(element) for element in list_of_values])
previous_value = ','.join([str(element.id) for element in list_of_values])
else: #new instance
old_human_values = ''
previous_value = ''
# description
description = '{} changed from "{}" to "{}"'.format(field, old_human_values, new_human_values)
else:
if self.instance.id: # update instance
previous_value = str(getattr(self.instance, field))
else:
previous_value = ''
new_value = str(self.cleaned_data[field])
description = '{} changed from "{}" to "{}"'.format(field, previous_value, new_value)
p = Provenance(modified_table=Appointment._meta.db_table,
modified_table_id=self.instance.id,
modification_author=self.user,
previous_value=previous_value,
new_value=new_value,
modification_description=description,
modified_field=field,
)
self.changes.append(p)
class AppointmentDetailForm(AppointmentForm): class AppointmentDetailForm(AppointmentForm):
class Meta: class Meta:
...@@ -64,10 +106,16 @@ class AppointmentEditForm(AppointmentForm): ...@@ -64,10 +106,16 @@ class AppointmentEditForm(AppointmentForm):
location = self.cleaned_data['location'] location = self.cleaned_data['location']
if self.user.locations.filter(id=location.id).count() == 0: if self.user.locations.filter(id=location.id).count() == 0:
self.add_error('location', "You cannot create appointment for this location") self.add_error('location', "You cannot create appointment for this location")
raise forms.ValidationError("You cannot create appointment for this location")
else: else:
return location return location
def clean(self):
if len(self.errors) == 0:
self.register_changes() # right before instance is changed
def save(self, commit=True): def save(self, commit=True):
appointment = super(AppointmentEditForm, self).save(commit) appointment = super(AppointmentEditForm, self).save(commit)
# if appointment date change, remove appointment_type links # if appointment date change, remove appointment_type links
if 'datetime_when' in self.changed_data: if 'datetime_when' in self.changed_data:
...@@ -89,6 +137,8 @@ class AppointmentEditForm(AppointmentForm): ...@@ -89,6 +137,8 @@ class AppointmentEditForm(AppointmentForm):
appointment_type_link = AppointmentTypeLink(appointment=appointment, appointment_type_link = AppointmentTypeLink(appointment=appointment,
appointment_type=appointment_type) appointment_type=appointment_type)
appointment_type_link.save() appointment_type_link.save()
self.save_changes()
return appointment return appointment
...@@ -117,12 +167,16 @@ class AppointmentAddForm(AppointmentForm): ...@@ -117,12 +167,16 @@ class AppointmentAddForm(AppointmentForm):
) )
fields['worker_assigned'].widget.attrs = {'class': 'search_worker_availability'} fields['worker_assigned'].widget.attrs = {'class': 'search_worker_availability'}
fields['datetime_when'].widget.attrs = {'class': 'start_date', 'placeholder': 'yyyy-mm-dd HH:MM', fields['datetime_when'].widget.attrs = {'class': 'start_date', 'placeholder': 'yyyy-mm-dd HH:MM',
'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}'} 'pattern': '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}'}
fields['length'].widget.attrs = {'class': 'appointment_duration'} fields['length'].widget.attrs = {'class': 'appointment_duration'}
self.fields = fields self.fields = fields
self.fields['location'].queryset = get_filter_locations(self.user) self.fields['location'].queryset = get_filter_locations(self.user)
def clean(self):
if len(self.errors) == 0:
self.register_changes() # right before instance is changed
def clean_location(self): def clean_location(self):
location = self.cleaned_data['location'] location = self.cleaned_data['location']
if self.user.locations.filter(id=location.id).count() == 0: if self.user.locations.filter(id=location.id).count() == 0:
...@@ -136,4 +190,5 @@ class AppointmentAddForm(AppointmentForm): ...@@ -136,4 +190,5 @@ class AppointmentAddForm(AppointmentForm):
for appointment_type in appointment_types: for appointment_type in appointment_types:
appointment_type_link = AppointmentTypeLink(appointment=appointment, appointment_type=appointment_type) appointment_type_link = AppointmentTypeLink(appointment=appointment, appointment_type=appointment_type)
appointment_type_link.save() appointment_type_link.save()
self.save_changes()
return appointment return appointment
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2020-03-19 10:40
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0140_auto_20190528_0953'),
]
operations = [
migrations.AlterField(
model_name='appointmenttype',
name='calendar_font_color',
field=models.CharField(default=b'#00000', max_length=2000, verbose_name=b'Calendar font color'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2020-03-19 10:50
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('web', '0141_auto_20200319_1040'),
]
operations = [
migrations.CreateModel(
name='Provenance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('modified_table', models.CharField(max_length=1024, verbose_name=b'Modified table')),
('modified_table_id', models.CharField(max_length=1024, verbose_name=b'Modified table row')),
('modification_date', models.DateTimeField(verbose_name=b'Modified on')),
('previous_value', models.CharField(blank=True, max_length=2048, null=True, verbose_name=b'Previous Value')),
('new_value', models.CharField(blank=True, max_length=2048, null=True, verbose_name=b'New Value')),
('modification_description', models.CharField(max_length=2048, verbose_name=b'Description')),
('modification_author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Worker', verbose_name=b'Worker who modified the row')),
],
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2020-03-19 11:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0142_provenance'),
]
operations = [
migrations.AlterField(
model_name='provenance',
name='modification_date',
field=models.DateTimeField(auto_now_add=True, verbose_name=b'Modified on'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2020-03-19 12:21
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0143_auto_20200319_1121'),
]
operations = [
migrations.AddField(
model_name='provenance',
name='modified_field',
field=models.CharField(default='', max_length=1024, verbose_name=b'Modified field'),
preserve_default=False,
),
migrations.AlterField(
model_name='provenance',
name='modification_description',
field=models.CharField(max_length=20480, verbose_name=b'Description'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2020-03-19 14:04
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0144_auto_20200319_1221'),
]
operations = [
migrations.AlterField(
model_name='provenance',
name='modified_field',
field=models.CharField(blank=b'', max_length=1024, verbose_name=b'Modified field'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2020-03-19 14:46
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('web', '0145_auto_20200319_1404'),
]
operations = [
migrations.AlterField(
model_name='provenance',
name='modification_author',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Worker', verbose_name=b'Worker who modified the row'),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2020-03-20 09:31
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web', '0146_auto_20200319_1446'),
]
operations = [
migrations.AlterField(
model_name='provenance',
name='modified_table_id',
field=models.IntegerField(default=0, verbose_name=b'Modified table row'),
),
]
...@@ -7,7 +7,7 @@ from django.db import migrations, models ...@@ -7,7 +7,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('web', '0140_auto_20190528_0953'), ('web', '0147_auto_20200320_0931'),
] ]
operations = [ operations = [
......
...@@ -8,7 +8,7 @@ from django.db import migrations ...@@ -8,7 +8,7 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('web', '0141_auto_20200319_1301'), ('web', '0148_auto_20200319_1301'),
] ]
operations = [ operations = [
......
...@@ -8,7 +8,7 @@ from django.db import migrations ...@@ -8,7 +8,7 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('web', '0142_auto_20200319_1415'), ('web', '0149_auto_20200319_1415'),
] ]
operations = [ operations = [
......
...@@ -8,7 +8,7 @@ from django.db import migrations ...@@ -8,7 +8,7 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('web', '0143_auto_20200319_1446'), ('web', '0150_auto_20200319_1446'),
] ]
operations = [ operations = [
......
...@@ -7,7 +7,7 @@ from django.db import migrations ...@@ -7,7 +7,7 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('web', '0144_auto_20200319_1518'), ('web', '0151_auto_20200319_1518'),
] ]
operations = [ operations = [
......
...@@ -8,7 +8,7 @@ from django.db import migrations, models ...@@ -8,7 +8,7 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('web', '0145_add_permissions_to_existing_workers'), ('web', '0152_add_permissions_to_existing_workers'),
] ]
operations = [ operations = [
......
...@@ -3,6 +3,7 @@ from __future__ import unicode_literals ...@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from django.contrib.auth.models import User from django.contrib.auth.models import User
from provenance import Provenance
from configuration_item import ConfigurationItem from configuration_item import ConfigurationItem
from flying_team import FlyingTeam from flying_team import FlyingTeam
from location import Location from location import Location
......
# coding=utf-8
from django.db import models
class Provenance(models.Model):
class Meta:
app_label = 'web'
modified_table = models.CharField(max_length=1024,
verbose_name='Modified table',
blank=False, null=False
)
modified_table_id = models.IntegerField(default=0, verbose_name='Modified table row', blank=False, null=False)
modification_date = models.DateTimeField(
verbose_name='Modified on',
null=False, blank=False,
auto_now_add=True
)
modification_author = models.ForeignKey("web.Worker",
verbose_name='Worker who modified the row',
null=True, blank=False
)
modified_field = models.CharField(max_length=1024,
verbose_name='Modified field',
blank='', null=False
)
previous_value = models.CharField(max_length=2048,
verbose_name='Previous Value',
blank=True, null=True)
new_value = models.CharField(max_length=2048,
verbose_name='New Value',
blank=True, null=True)
modification_description = models.CharField(max_length=20480,
verbose_name='Description',
blank=False, null=False
)
\ No newline at end of file
...@@ -6,7 +6,7 @@ from django.db.models.signals import post_save ...@@ -6,7 +6,7 @@ from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from constants import SEX_CHOICES, COUNTRY_OTHER_ID from constants import SEX_CHOICES, COUNTRY_OTHER_ID
from web.models import Country, Visit, Appointment from web.models import Country, Visit, Appointment, Provenance
from . import Language from . import Language
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -154,4 +154,11 @@ class Subject(models.Model): ...@@ -154,4 +154,11 @@ class Subject(models.Model):
@receiver(post_save, sender=Subject) @receiver(post_save, sender=Subject)
def set_as_deceased(sender, instance, **kwargs): def set_as_deceased(sender, instance, **kwargs):
if instance.dead: if instance.dead:
p = Provenance(modified_table = Subject._meta.db_table,
modified_table_id = instance.id, modification_author = None,
previous_value = instance.dead, new_value = True,
modification_description = 'Subject "{}" marked as dead'.format(instance),
modified_field = 'dead',
)
instance.mark_as_dead() instance.mark_as_dead()
p.save()
\ No newline at end of file
<li><a href="{% url 'web.views.provenance' %}"><i class="fa fa-dashboard"></i> Dashboard</a></li>
<li class="active"><a href="{% url 'web.views.provenance' %}">Provenance</a></li>
\ No newline at end of file
{% extends "_base.html" %}
{% load static %}
{% block styles %}
{{ block.super }}
<!-- DataTables -->
<link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
{% endblock styles %}
{% block ui_active_tab %}'provenance'{% endblock ui_active_tab %}
{% block page_header %}Provenance{% endblock page_header %}
{% block page_description %}{% endblock page_description %}
{% block breadcrumb %}
{% include "languages/breadcrumb.html" %}
{% endblock breadcrumb %}
{% block maincontent %}
<div class="box-body">
<table id="table" class="table table-bordered table-striped">
<thead>
<tr>
<th>Table</th>
<th>Row</th>
<th>Date</th>
<th>Author</th>
<th>Field</th>
<th>Previous Value</th>
<th>New Value</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{% for provenance in provenances %}
<tr>
<td>{{ provenance.modified_table }}</td>
<td>{{ provenance.modified_table_id }}</td>
<td>{{ provenance.modification_date }}</td>
<td>{{ provenance.modification_author }}</td>
<td>{{ provenance.modified_field }}</td>
<td>{{ provenance.previous_value }}</td>
<td>{{ provenance.new_value }}</td>
<td>{{ provenance.modification_description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock maincontent %}
{% block scripts %}
{{ block.super }}
<script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script>
<script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
<script>
$(function () {
$('#table').DataTable({
"paging": true,
"lengthChange": false,
"searching": true,
"ordering": true,
"info": true,
"autoWidth": false
});
});
</script>
{% endblock scripts %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment