diff --git a/FAIRshake/settings.py b/FAIRshake/settings.py index 22e95af..0c38e70 100644 --- a/FAIRshake/settings.py +++ b/FAIRshake/settings.py @@ -63,6 +63,7 @@ 'corsheaders', 'ajax_select', 'analytical', + 'versions_tests', 'allauth', 'allauth.account', 'allauth.socialaccount', @@ -73,6 +74,7 @@ 'extensions.allauth_ex', 'extensions.drf_yasg_ex', 'extensions.rest_auth_ex', + 'extensions.versions_ex', 'extensions.rest_framework_ex', 'FAIRshakeHub', 'FAIRshakeAPI', diff --git a/FAIRshakeAPI/assessments/fairsharing/__init__.py b/FAIRshakeAPI/assessments/fairsharing/__init__.py index 5b3ae28..58e9cbf 100644 --- a/FAIRshakeAPI/assessments/fairsharing/__init__.py +++ b/FAIRshakeAPI/assessments/fairsharing/__init__.py @@ -1,61 +1,59 @@ +import re from django.conf import settings from scripts.pyswagger_wrapper import SwaggerClient +url_re = re.compile(r'^https?://doi.org/(.+)$') + +metric_to_attr = { + 'metric:9': 'licence', + 'metric:60': 'homepage', + 'metric:101': 'taxonomies', + 'metric:102': 'domains', +} + class Assessment: inputs = [ - 'target:fairsharing' + 'target:url' ] outputs = [ # 'target:url', # 'target:description', # 'target:title', - # 'target:doi', # 'target:authors' - 'metric:9', # license - 'metric:60', # title - 'metric:101', # taxonomies - 'metric:102', # domains - ] + ] + list(metric_to_attr.keys()) + @classmethod def perform(kls, inputs): - client = SwaggerClient( - 'https://fairsharing.org/api/?format=openapi', - headers={ - 'Api-Key': settings.ASSESSMENT_CONFIG['fairsharing']['api-key'], - } - ) - results = client.actions.database_summary_read.call( - bsg_id=inputs['target:fairsharing'] - ) - return { - # 'target:url': { - # 'answer': results['data'].get('homepage'), - # 'comment': results['data'].get('homepage'), - # }, - # 'target:description': { - # 'answer': results['data'].get('description'), - # 'comment': results['data'].get('description'), - # }, - # 'target:title': { - # 'answer': results['data'].get('name'), - # 'comment': results['data'].get('name'), - # }, - 'metric:9': { - 'answer': 'yes' if results.get('licence') else 'no', - 'comment': results.get('licence'), - }, - 'metric:60': { - 'answer': 'yes' if results['data'].get('homepage') is not None else 'no', - 'comment': results['data'].get('homepage'), - }, - 'metric:101': { - 'answer': 'yes' if results['data'].get('taxonomies') else 'no', - 'comment': results['data'].get('taxonomies'), - }, - 'metric:102': { - 'answer': 'yes' if results['data'].get('domains') else 'no', - 'comment': results['data'].get('domains'), - }, - # 'target:doi': results['data'].get('doi'), - # 'target:authors': results['data'].get('maintainers'), - } + url = inputs['target:url'] + dois = [m.group(1) for m in map(url_re.match, url.splitlines()) if m] + + if dois: + client = SwaggerClient( + 'https://fairsharing.org/api/?format=openapi', + headers={ + 'Api-Key': settings.ASSESSMENT_CONFIG['fairsharing']['api-key'], + } + ) + + results = [ + result + for doi in dois + for result in client.actions.database_summary_list.call( + doi=doi, + )['results'] + ] + + if len(results) > 1: + logging.warn('More than 1 DOI was identified in the fairsharing database! (%s)' % (url)) + if len(results) >= 1: + data = results[0]['data'] + else: + data = None + + return { + key: { + 'answer': 'yes' if data.get(attr) else 'no', + 'comment': data.get(attr), + } + for key, attr in metric_to_attr.items() + } if data else {key: {} for key in metric_to_attr.keys()} diff --git a/FAIRshakeAPI/forms.py b/FAIRshakeAPI/forms.py index 85c93be..473e14e 100644 --- a/FAIRshakeAPI/forms.py +++ b/FAIRshakeAPI/forms.py @@ -16,7 +16,21 @@ def __init__(self, *args, **kwargs): help_text=None, initial=getattr(self.instance, child).all() if self.instance and self.instance.id else [], ) - + + def clean_slug(self): + slug = self.cleaned_data.get('slug') + try: + if any([ + self.Meta.model.objects.current.get(slug=slug) != self.instance, + self.Meta.model.objects.get(id=slug) != self.instance, + ]): + raise forms.ValidationError( + 'Slug was already taken, please try something different.' + ) + except self.Meta.model.DoesNotExist: + pass + return slug + def save(self, *args, commit=True, **kwargs): ''' Explicitly add children for children in the reverse direction. ''' @@ -43,6 +57,7 @@ class Meta: 'image', 'tags', 'type', + 'slug', 'digital_objects', 'authors', ) @@ -57,6 +72,7 @@ class Meta: 'image', 'tags', 'type', + 'slug', 'rubrics', 'authors', ) @@ -71,6 +87,7 @@ class Meta: 'image', 'tags', 'type', + 'slug', 'license', 'metrics', 'authors', @@ -86,27 +103,11 @@ class Meta: 'image', 'tags', 'type', + 'slug', 'license', 'rationale', 'principle', 'fairmetrics', - 'authors', - ) - -class AssessmentForm(forms.ModelForm): - def __init__(self, *args, **kwargs): - super(AssessmentForm, self).__init__(*args, **kwargs) - - self.fields['target'].widget = forms.HiddenInput() - self.fields['rubric'].widget = forms.HiddenInput() - self.fields['project'].widget = forms.HiddenInput() - - class Meta: - model = models.Assessment - fields = ( - 'target', - 'rubric', - 'project', ) class AnswerForm(forms.ModelForm): diff --git a/FAIRshakeAPI/migrations/0005_auto_20180910_1752.py b/FAIRshakeAPI/migrations/0005_auto_20180910_1752.py new file mode 100644 index 0000000..8087463 --- /dev/null +++ b/FAIRshakeAPI/migrations/0005_auto_20180910_1752.py @@ -0,0 +1,210 @@ +# Generated by Django 2.0.7 on 2018-09-10 17:52 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import extensions.versions_ex.models +import versions.fields +import uuid + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0004_auto_20180823_1910'), + ] + + operations = [ + migrations.CreateModel( + name='AnswerNew', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('answer', models.TextField(blank=True, default='')), + ('comment', models.TextField(blank=True, default='')), + ('url_comment', models.TextField(blank=True, default='')), + ], + options={ + 'verbose_name': 'answer', + 'verbose_name_plural': 'answers', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='AssessmentNew', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('methodology', models.TextField(blank=True, choices=[('self', 'Digital Object Creator Assessment'), ('user', 'Independent User Assessment'), ('auto', 'Automatic Assessment'), ('test', 'Test Assessment')], max_length=16)), + ('assessor', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'assessment', + 'verbose_name_plural': 'assessments', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='AssessmentRequestNew', + fields=[ + ('id', models.UUIDField(primary_key=True, default=uuid.uuid4, serialize=False)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('assessment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='request', to='FAIRshakeAPI.AssessmentNew')), + ('requestor', models.ForeignKey(blank=True, default='', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'assessment_request', + 'verbose_name_plural': 'assessment_requests', + 'ordering': ['id'], + }, + ), + migrations.CreateModel( + name='DigitalObjectNew', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('', 'Other'), ('any', 'Any Digital Object'), ('data', 'Dataset'), ('repo', 'Repository'), ('test', 'Test Object'), ('tool', 'Tool')], default='', max_length=16)), + ('url', models.CharField(max_length=255)), + ('title', models.CharField(blank=True, default='', max_length=255)), + ('fairsharing', models.CharField(blank=True, default='', max_length=255)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'digital_object', + 'verbose_name_plural': 'digital_objects', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='MetricNew', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('url', models.CharField(blank=True, max_length=255)), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('yesnobut', 'Yes no or but question'), ('text', 'Simple textbox input'), ('url', 'A url input')], default='yesnobut', max_length=16)), + ('license', models.CharField(blank=True, default='', max_length=255)), + ('rationale', models.TextField(blank=True, default='')), + ('principle', models.CharField(blank=True, choices=[('F', 'Findability'), ('A', 'Accessibility'), ('I', 'Interoperability'), ('R', 'Reusability')], default='', max_length=16)), + ('fairmetrics', models.CharField(blank=True, default='', max_length=255)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'metric', + 'verbose_name_plural': 'metrics', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='ProjectNew', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('url', models.CharField(blank=True, max_length=255)), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('', 'Other'), ('any', 'Any Digital Object'), ('data', 'Dataset'), ('repo', 'Repository'), ('test', 'Test Object'), ('tool', 'Tool')], default='', max_length=16)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ('digital_objects', versions.fields.VersionedManyToManyField(blank=True, related_name='projects', to='FAIRshakeAPI.DigitalObjectNew')), + ], + options={ + 'verbose_name': 'project', + 'verbose_name_plural': 'projects', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='RubricNew', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('url', models.CharField(blank=True, max_length=255)), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('', 'Other'), ('any', 'Any Digital Object'), ('data', 'Dataset'), ('repo', 'Repository'), ('test', 'Test Object'), ('tool', 'Tool')], default='', max_length=16)), + ('license', models.CharField(blank=True, default='', max_length=255)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ('metrics', versions.fields.VersionedManyToManyField(blank=True, related_name='rubrics', to='FAIRshakeAPI.MetricNew')), + ], + options={ + 'verbose_name': 'rubric', + 'verbose_name_plural': 'rubrics', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.AddField( + model_name='digitalobjectnew', + name='rubrics', + field=versions.fields.VersionedManyToManyField(blank=True, related_name='digital_objects', to='FAIRshakeAPI.RubricNew'), + ), + migrations.AddField( + model_name='assessmentnew', + name='project', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assessments', to='FAIRshakeAPI.ProjectNew'), + ), + migrations.AddField( + model_name='assessmentnew', + name='rubric', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='assessments', to='FAIRshakeAPI.RubricNew'), + ), + migrations.AddField( + model_name='assessmentnew', + name='target', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='assessments', to='FAIRshakeAPI.DigitalObjectNew'), + ), + migrations.AddField( + model_name='answernew', + name='assessment', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='FAIRshakeAPI.AssessmentNew'), + ), + migrations.AddField( + model_name='answernew', + name='metric', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='FAIRshakeAPI.MetricNew'), + ), + ] diff --git a/FAIRshakeAPI/migrations/0006_auto_20180910_1752.py b/FAIRshakeAPI/migrations/0006_auto_20180910_1752.py new file mode 100644 index 0000000..0cb78aa --- /dev/null +++ b/FAIRshakeAPI/migrations/0006_auto_20180910_1752.py @@ -0,0 +1,132 @@ +from django.db import migrations, transaction + +def migrate_db(forwards=None): + def migrate(apps, schema_editor): + Metric = apps.get_model('FAIRshakeAPI', 'Metric' if forwards else 'MetricNew') + MetricNew = apps.get_model('FAIRshakeAPI', 'MetricNew' if forwards else 'Metric') + + old_metric_2_new = {} + for metric in Metric.objects.all(): + new_metric = MetricNew.objects.create( + title=metric.title, + url=metric.url, + description=metric.description, + image=metric.image, + tags=metric.tags, + type=metric.type, + license=metric.license, + rationale=metric.rationale, + principle=metric.principle, + fairmetrics=metric.fairmetrics, + ) + for author in metric.authors.all(): + new_metric.authors.add(author) + old_metric_2_new[metric] = new_metric + + Rubric = apps.get_model('FAIRshakeAPI', 'Rubric' if forwards else 'RubricNew') + RubricNew = apps.get_model('FAIRshakeAPI', 'RubricNew' if forwards else 'Rubric') + old_rubric_2_new = {} + for rubric in Rubric.objects.all(): + new_rubric = RubricNew.objects.create( + title=rubric.title, + url=rubric.url, + description=rubric.description, + image=rubric.image, + tags=rubric.tags, + type=rubric.type, + license=rubric.license, + ) + for author in rubric.authors.all(): + new_rubric.authors.add(author) + for metric in rubric.metrics.all(): + new_rubric.metrics.add(old_metric_2_new[metric]) + old_rubric_2_new[rubric] = new_rubric + + DigitalObject = apps.get_model('FAIRshakeAPI', 'DigitalObject' if forwards else 'DigitalObjectNew') + DigitalObjectNew = apps.get_model('FAIRshakeAPI', 'DigitalObjectNew' if forwards else 'DigitalObject') + old_obj_2_new = {} + for obj in DigitalObject.objects.all(): + new_obj = DigitalObjectNew.objects.create( + title=obj.title, + url=obj.url, + description=obj.description, + image=obj.image, + tags=obj.tags, + type=obj.type, + fairsharing=obj.fairsharing, + ) + for author in obj.authors.all(): + new_obj.authors.add(author) + for rubric in obj.rubrics.all(): + new_obj.rubrics.add(old_rubric_2_new[rubric]) + old_obj_2_new[obj] = new_obj + + Project = apps.get_model('FAIRshakeAPI', 'Project' if forwards else 'ProjectNew') + ProjectNew = apps.get_model('FAIRshakeAPI', 'ProjectNew' if forwards else 'Project') + old_project_2_new = {} + for project in Project.objects.all(): + new_project = ProjectNew.objects.create( + title=project.title, + url=project.url, + description=project.description, + image=project.image, + tags=project.tags, + type=project.type, + ) + for author in project.authors.all(): + new_project.authors.add(author) + for obj in project.digital_objects.all(): + new_project.digital_objects.add(old_obj_2_new[obj]) + old_project_2_new[project] = new_project + + Assessment = apps.get_model('FAIRshakeAPI', 'Assessment' if forwards else 'AssessmentNew') + AssessmentNew = apps.get_model('FAIRshakeAPI', 'AssessmentNew' if forwards else 'Assessment') + old_assessment_2_new = {} + for assessment in Assessment.objects.all(): + new_assessment = AssessmentNew.objects.create( + project=old_project_2_new[assessment.project] if assessment.project is not None else None, + target=old_obj_2_new[assessment.target], + rubric=old_rubric_2_new[assessment.rubric], + methodology=assessment.methodology, + assessor=assessment.assessor, + ) + old_assessment_2_new[assessment] = new_assessment + + Answer = apps.get_model('FAIRshakeAPI', 'Answer' if forwards else 'AnswerNew') + AnswerNew = apps.get_model('FAIRshakeAPI', 'AnswerNew' if forwards else 'Answer') + old_answer_2_new = {} + for answer in Answer.objects.all(): + new_answer = AnswerNew.objects.create( + assessment=old_assessment_2_new[answer.assessment], + metric=old_metric_2_new[answer.metric], + answer=answer.answer, + comment=answer.comment, + url_comment=answer.url_comment, + ) + old_answer_2_new[answer] = new_answer + + AssessmentRequest = apps.get_model('FAIRshakeAPI', 'AssessmentRequest' if forwards else 'AssessmentRequestNew') + AssessmentRequestNew = apps.get_model('FAIRshakeAPI', 'AssessmentRequestNew' if forwards else 'AssessmentRequest') + old_assessment_request_2_new = {} + for assessment_request in AssessmentRequest.objects.all(): + new_assessment_request = AssessmentRequestNew.objects.create( + assessment=old_assessment_2_new[assessment_request.assessment], + requestor=assessment_request.requestor, + timestamp=assessment_request.timestamp, + ) + old_assessment_request_2_new[assessment_request] = new_assessment_request + + return migrate + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0005_auto_20180910_1752'), + ] + + operations = [ + migrations.RunPython( + migrate_db(forwards=True), + migrate_db(forwards=False), + ) + ] diff --git a/FAIRshakeAPI/migrations/0007_auto_20180910_1756.py b/FAIRshakeAPI/migrations/0007_auto_20180910_1756.py new file mode 100644 index 0000000..7c6ea99 --- /dev/null +++ b/FAIRshakeAPI/migrations/0007_auto_20180910_1756.py @@ -0,0 +1,34 @@ +# Generated by Django 2.0.7 on 2018-09-10 17:56 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0006_auto_20180910_1752'), + ] + + operations = [ + migrations.DeleteModel( + name='Answer', + ), + migrations.DeleteModel( + name='Assessment', + ), + migrations.DeleteModel( + name='AssessmentRequest', + ), + migrations.DeleteModel( + name='DigitalObject', + ), + migrations.DeleteModel( + name='Metric', + ), + migrations.DeleteModel( + name='Project', + ), + migrations.DeleteModel( + name='Rubric', + ), + ] diff --git a/FAIRshakeAPI/migrations/0008_auto_20180910_1933.py b/FAIRshakeAPI/migrations/0008_auto_20180910_1933.py new file mode 100644 index 0000000..8f6d2a7 --- /dev/null +++ b/FAIRshakeAPI/migrations/0008_auto_20180910_1933.py @@ -0,0 +1,211 @@ +# Generated by Django 2.0.7 on 2018-09-10 19:33 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import extensions.versions_ex.models +import uuid +import versions.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0007_auto_20180910_1756'), + ] + + operations = [ + migrations.CreateModel( + name='Answer', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('answer', models.TextField(blank=True, default='')), + ('comment', models.TextField(blank=True, default='')), + ('url_comment', models.TextField(blank=True, default='')), + ], + options={ + 'verbose_name': 'answer', + 'verbose_name_plural': 'answers', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='Assessment', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('methodology', models.TextField(blank=True, choices=[('self', 'Digital Object Creator Assessment'), ('user', 'Independent User Assessment'), ('auto', 'Automatic Assessment'), ('test', 'Test Assessment')], max_length=16)), + ('assessor', models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'assessment', + 'verbose_name_plural': 'assessments', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='AssessmentRequest', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('assessment', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='request', to='FAIRshakeAPI.Assessment')), + ('requestor', models.ForeignKey(blank=True, default='', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'assessment_request', + 'verbose_name_plural': 'assessment_requests', + 'ordering': ['id'], + }, + ), + migrations.CreateModel( + name='DigitalObject', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('', 'Other'), ('any', 'Any Digital Object'), ('data', 'Dataset'), ('repo', 'Repository'), ('test', 'Test Object'), ('tool', 'Tool')], default='', max_length=16)), + ('url', models.CharField(max_length=255)), + ('title', models.CharField(blank=True, default='', max_length=255)), + ('fairsharing', models.CharField(blank=True, default='', max_length=255)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'digital_object', + 'verbose_name_plural': 'digital_objects', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='Metric', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('url', models.CharField(blank=True, max_length=255)), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('yesnobut', 'Yes no or but question'), ('text', 'Simple textbox input'), ('url', 'A url input')], default='yesnobut', max_length=16)), + ('license', models.CharField(blank=True, default='', max_length=255)), + ('rationale', models.TextField(blank=True, default='')), + ('principle', models.CharField(blank=True, choices=[('F', 'Findability'), ('A', 'Accessibility'), ('I', 'Interoperability'), ('R', 'Reusability')], default='', max_length=16)), + ('fairmetrics', models.CharField(blank=True, default='', max_length=255)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'metric', + 'verbose_name_plural': 'metrics', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='Project', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('url', models.CharField(blank=True, max_length=255)), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('', 'Other'), ('any', 'Any Digital Object'), ('data', 'Dataset'), ('repo', 'Repository'), ('test', 'Test Object'), ('tool', 'Tool')], default='', max_length=16)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ('digital_objects', versions.fields.VersionedManyToManyField(blank=True, related_name='projects', to='FAIRshakeAPI.DigitalObject')), + ], + options={ + 'verbose_name': 'project', + 'verbose_name_plural': 'projects', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.CreateModel( + name='Rubric', + fields=[ + ('id', models.UUIDField(primary_key=True, serialize=False)), + ('identity', models.UUIDField()), + ('version_start_date', models.DateTimeField()), + ('version_end_date', models.DateTimeField(blank=True, default=None, null=True)), + ('version_birth_date', models.DateTimeField()), + ('title', models.CharField(max_length=255)), + ('url', models.CharField(blank=True, max_length=255)), + ('description', models.TextField(blank=True, default='')), + ('image', models.CharField(blank=True, default='', max_length=255)), + ('tags', models.CharField(blank=True, max_length=255)), + ('type', models.CharField(blank=True, choices=[('', 'Other'), ('any', 'Any Digital Object'), ('data', 'Dataset'), ('repo', 'Repository'), ('test', 'Test Object'), ('tool', 'Tool')], default='', max_length=16)), + ('license', models.CharField(blank=True, default='', max_length=255)), + ('authors', versions.fields.VersionedManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)), + ('metrics', versions.fields.VersionedManyToManyField(blank=True, related_name='rubrics', to='FAIRshakeAPI.Metric')), + ], + options={ + 'verbose_name': 'rubric', + 'verbose_name_plural': 'rubrics', + 'ordering': ['id'], + }, + managers=[ + ('objects', extensions.versions_ex.models.VersionManagerEx()), + ], + ), + migrations.AddField( + model_name='digitalobject', + name='rubrics', + field=versions.fields.VersionedManyToManyField(blank=True, related_name='digital_objects', to='FAIRshakeAPI.Rubric'), + ), + migrations.AddField( + model_name='assessment', + name='project', + field=models.ForeignKey(blank=True, editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assessments', to='FAIRshakeAPI.Project'), + ), + migrations.AddField( + model_name='assessment', + name='rubric', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='assessments', to='FAIRshakeAPI.Rubric'), + ), + migrations.AddField( + model_name='assessment', + name='target', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='assessments', to='FAIRshakeAPI.DigitalObject'), + ), + migrations.AddField( + model_name='answer', + name='assessment', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='FAIRshakeAPI.Assessment'), + ), + migrations.AddField( + model_name='answer', + name='metric', + field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='FAIRshakeAPI.Metric'), + ), + ] diff --git a/FAIRshakeAPI/migrations/0009_auto_20180910_1933.py b/FAIRshakeAPI/migrations/0009_auto_20180910_1933.py new file mode 100644 index 0000000..5f6d860 --- /dev/null +++ b/FAIRshakeAPI/migrations/0009_auto_20180910_1933.py @@ -0,0 +1,132 @@ +from django.db import migrations, transaction + +def migrate_db(forwards=None): + def migrate(apps, schema_editor): + Metric = apps.get_model('FAIRshakeAPI', 'Metric' if forwards else 'MetricNew') + MetricNew = apps.get_model('FAIRshakeAPI', 'MetricNew' if forwards else 'Metric') + + old_metric_2_new = {} + for metric in Metric.objects.all(): + new_metric = MetricNew.objects.create( + title=metric.title, + url=metric.url, + description=metric.description, + image=metric.image, + tags=metric.tags, + type=metric.type, + license=metric.license, + rationale=metric.rationale, + principle=metric.principle, + fairmetrics=metric.fairmetrics, + ) + for author in metric.authors.all(): + new_metric.authors.add(author) + old_metric_2_new[metric] = new_metric + + Rubric = apps.get_model('FAIRshakeAPI', 'Rubric' if forwards else 'RubricNew') + RubricNew = apps.get_model('FAIRshakeAPI', 'RubricNew' if forwards else 'Rubric') + old_rubric_2_new = {} + for rubric in Rubric.objects.all(): + new_rubric = RubricNew.objects.create( + title=rubric.title, + url=rubric.url, + description=rubric.description, + image=rubric.image, + tags=rubric.tags, + type=rubric.type, + license=rubric.license, + ) + for author in rubric.authors.all(): + new_rubric.authors.add(author) + for metric in rubric.metrics.all(): + new_rubric.metrics.add(old_metric_2_new[metric]) + old_rubric_2_new[rubric] = new_rubric + + DigitalObject = apps.get_model('FAIRshakeAPI', 'DigitalObject' if forwards else 'DigitalObjectNew') + DigitalObjectNew = apps.get_model('FAIRshakeAPI', 'DigitalObjectNew' if forwards else 'DigitalObject') + old_obj_2_new = {} + for obj in DigitalObject.objects.all(): + new_obj = DigitalObjectNew.objects.create( + title=obj.title, + url=obj.url, + description=obj.description, + image=obj.image, + tags=obj.tags, + type=obj.type, + fairsharing=obj.fairsharing, + ) + for author in obj.authors.all(): + new_obj.authors.add(author) + for rubric in obj.rubrics.all(): + new_obj.rubrics.add(old_rubric_2_new[rubric]) + old_obj_2_new[obj] = new_obj + + Project = apps.get_model('FAIRshakeAPI', 'Project' if forwards else 'ProjectNew') + ProjectNew = apps.get_model('FAIRshakeAPI', 'ProjectNew' if forwards else 'Project') + old_project_2_new = {} + for project in Project.objects.all(): + new_project = ProjectNew.objects.create( + title=project.title, + url=project.url, + description=project.description, + image=project.image, + tags=project.tags, + type=project.type, + ) + for author in project.authors.all(): + new_project.authors.add(author) + for obj in project.digital_objects.all(): + new_project.digital_objects.add(old_obj_2_new[obj]) + old_project_2_new[project] = new_project + + Assessment = apps.get_model('FAIRshakeAPI', 'Assessment' if forwards else 'AssessmentNew') + AssessmentNew = apps.get_model('FAIRshakeAPI', 'AssessmentNew' if forwards else 'Assessment') + old_assessment_2_new = {} + for assessment in Assessment.objects.all(): + new_assessment = AssessmentNew.objects.create( + project=old_project_2_new[assessment.project] if assessment.project is not None else None, + target=old_obj_2_new[assessment.target], + rubric=old_rubric_2_new[assessment.rubric], + methodology=assessment.methodology, + assessor=assessment.assessor, + ) + old_assessment_2_new[assessment] = new_assessment + + Answer = apps.get_model('FAIRshakeAPI', 'Answer' if forwards else 'AnswerNew') + AnswerNew = apps.get_model('FAIRshakeAPI', 'AnswerNew' if forwards else 'Answer') + old_answer_2_new = {} + for answer in Answer.objects.all(): + new_answer = AnswerNew.objects.create( + assessment=old_assessment_2_new[answer.assessment], + metric=old_metric_2_new[answer.metric], + answer=answer.answer, + comment=answer.comment, + url_comment=answer.url_comment, + ) + old_answer_2_new[answer] = new_answer + + AssessmentRequest = apps.get_model('FAIRshakeAPI', 'AssessmentRequest' if forwards else 'AssessmentRequestNew') + AssessmentRequestNew = apps.get_model('FAIRshakeAPI', 'AssessmentRequestNew' if forwards else 'AssessmentRequest') + old_assessment_request_2_new = {} + for assessment_request in AssessmentRequest.objects.all(): + new_assessment_request = AssessmentRequestNew.objects.create( + assessment=old_assessment_2_new[assessment_request.assessment], + requestor=assessment_request.requestor, + timestamp=assessment_request.timestamp, + ) + old_assessment_request_2_new[assessment_request] = new_assessment_request + + return migrate + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0008_auto_20180910_1933'), + ] + + operations = [ + migrations.RunPython( + migrate_db(forwards=False), + migrate_db(forwards=True), + ) + ] diff --git a/FAIRshakeAPI/migrations/0010_auto_20180910_1935.py b/FAIRshakeAPI/migrations/0010_auto_20180910_1935.py new file mode 100644 index 0000000..c34f832 --- /dev/null +++ b/FAIRshakeAPI/migrations/0010_auto_20180910_1935.py @@ -0,0 +1,94 @@ +# Generated by Django 2.0.7 on 2018-09-10 19:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0009_auto_20180910_1933'), + ] + + operations = [ + migrations.RemoveField( + model_name='answernew', + name='assessment', + ), + migrations.RemoveField( + model_name='answernew', + name='metric', + ), + migrations.RemoveField( + model_name='assessmentnew', + name='assessor', + ), + migrations.RemoveField( + model_name='assessmentnew', + name='project', + ), + migrations.RemoveField( + model_name='assessmentnew', + name='rubric', + ), + migrations.RemoveField( + model_name='assessmentnew', + name='target', + ), + migrations.RemoveField( + model_name='assessmentrequestnew', + name='assessment', + ), + migrations.RemoveField( + model_name='assessmentrequestnew', + name='requestor', + ), + migrations.RemoveField( + model_name='digitalobjectnew', + name='authors', + ), + migrations.RemoveField( + model_name='digitalobjectnew', + name='rubrics', + ), + migrations.RemoveField( + model_name='metricnew', + name='authors', + ), + migrations.RemoveField( + model_name='projectnew', + name='authors', + ), + migrations.RemoveField( + model_name='projectnew', + name='digital_objects', + ), + migrations.RemoveField( + model_name='rubricnew', + name='authors', + ), + migrations.RemoveField( + model_name='rubricnew', + name='metrics', + ), + migrations.DeleteModel( + name='AnswerNew', + ), + migrations.DeleteModel( + name='AssessmentNew', + ), + migrations.DeleteModel( + name='AssessmentRequestNew', + ), + migrations.DeleteModel( + name='DigitalObjectNew', + ), + migrations.DeleteModel( + name='MetricNew', + ), + migrations.DeleteModel( + name='ProjectNew', + ), + migrations.DeleteModel( + name='RubricNew', + ), + ] diff --git a/FAIRshakeAPI/migrations/0011_auto_20180911_1605.py b/FAIRshakeAPI/migrations/0011_auto_20180911_1605.py new file mode 100644 index 0000000..33507a3 --- /dev/null +++ b/FAIRshakeAPI/migrations/0011_auto_20180911_1605.py @@ -0,0 +1,80 @@ +# Generated by Django 2.0.7 on 2018-09-11 16:05 + +from django.db import migrations +import extensions.versions_ex.fields +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0010_auto_20180910_1935'), + ] + + operations = [ + migrations.AlterField( + model_name='answer', + name='id', + field=extensions.versions_ex.fields.CustomUUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='answer', + name='identity', + field=extensions.versions_ex.fields.CustomUUIDField(), + ), + migrations.AlterField( + model_name='assessment', + name='id', + field=extensions.versions_ex.fields.CustomUUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='assessment', + name='identity', + field=extensions.versions_ex.fields.CustomUUIDField(), + ), + migrations.AlterField( + model_name='assessmentrequest', + name='id', + field=extensions.versions_ex.fields.CustomUUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='digitalobject', + name='id', + field=extensions.versions_ex.fields.CustomUUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='digitalobject', + name='identity', + field=extensions.versions_ex.fields.CustomUUIDField(), + ), + migrations.AlterField( + model_name='metric', + name='id', + field=extensions.versions_ex.fields.CustomUUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='metric', + name='identity', + field=extensions.versions_ex.fields.CustomUUIDField(), + ), + migrations.AlterField( + model_name='project', + name='id', + field=extensions.versions_ex.fields.CustomUUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='project', + name='identity', + field=extensions.versions_ex.fields.CustomUUIDField(), + ), + migrations.AlterField( + model_name='rubric', + name='id', + field=extensions.versions_ex.fields.CustomUUIDField(default=uuid.uuid4, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='rubric', + name='identity', + field=extensions.versions_ex.fields.CustomUUIDField(), + ), + ] diff --git a/FAIRshakeAPI/migrations/0012_auto_20180912_2050.py b/FAIRshakeAPI/migrations/0012_auto_20180912_2050.py new file mode 100644 index 0000000..4e57903 --- /dev/null +++ b/FAIRshakeAPI/migrations/0012_auto_20180912_2050.py @@ -0,0 +1,38 @@ +# Generated by Django 2.0.7 on 2018-09-12 20:50 + +import builtins +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0011_auto_20180911_1605'), + ] + + operations = [ + migrations.AddField( + model_name='digitalobject', + name='slug', + field=models.CharField(max_length=255, default=None, null=True), + preserve_default=False, + ), + migrations.AddField( + model_name='metric', + name='slug', + field=models.CharField(max_length=255, default=None, null=True), + preserve_default=False, + ), + migrations.AddField( + model_name='project', + name='slug', + field=models.CharField(max_length=255, default=None, null=True), + preserve_default=False, + ), + migrations.AddField( + model_name='rubric', + name='slug', + field=models.CharField(max_length=255, default=None, null=True), + preserve_default=False, + ), + ] diff --git a/FAIRshakeAPI/migrations/0013_auto_20180912_2051.py b/FAIRshakeAPI/migrations/0013_auto_20180912_2051.py new file mode 100644 index 0000000..7246fee --- /dev/null +++ b/FAIRshakeAPI/migrations/0013_auto_20180912_2051.py @@ -0,0 +1,36 @@ +# Generated by Django 2.0.7 on 2018-09-12 20:51 + +from django.db import migrations + +def migrate(apps, schema_editor): + Project = apps.get_model('FAIRshakeAPI', 'Project') + for project in Project.objects.all(): + project.slug = project.id + project.save() + + DigitalObject = apps.get_model('FAIRshakeAPI', 'DigitalObject') + for obj in DigitalObject.objects.all(): + obj.slug = obj.id + obj.save() + + Rubric = apps.get_model('FAIRshakeAPI', 'Rubric') + for rubric in Rubric.objects.all(): + rubric.slug = rubric.id + rubric.save() + + Metric = apps.get_model('FAIRshakeAPI', 'Metric') + for metric in Metric.objects.all(): + metric.slug = metric.id + metric.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0012_auto_20180912_2050'), + ] + + operations = [ + migrations.RunPython( + migrate + ) + ] diff --git a/FAIRshakeAPI/migrations/0014_auto_20180912_2054.py b/FAIRshakeAPI/migrations/0014_auto_20180912_2054.py new file mode 100644 index 0000000..dbebdc5 --- /dev/null +++ b/FAIRshakeAPI/migrations/0014_auto_20180912_2054.py @@ -0,0 +1,33 @@ +# Generated by Django 2.0.7 on 2018-09-12 20:54 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0013_auto_20180912_2051'), + ] + + operations = [ + migrations.AlterField( + model_name='digitalobject', + name='slug', + field=models.CharField(max_length=255, unique=True), + ), + migrations.AlterField( + model_name='metric', + name='slug', + field=models.CharField(max_length=255, unique=True), + ), + migrations.AlterField( + model_name='project', + name='slug', + field=models.CharField(max_length=255, unique=True), + ), + migrations.AlterField( + model_name='rubric', + name='slug', + field=models.CharField(max_length=255, unique=True), + ), + ] diff --git a/FAIRshakeAPI/migrations/0015_remove_digitalobject_fairsharing.py b/FAIRshakeAPI/migrations/0015_remove_digitalobject_fairsharing.py new file mode 100644 index 0000000..2d63bd9 --- /dev/null +++ b/FAIRshakeAPI/migrations/0015_remove_digitalobject_fairsharing.py @@ -0,0 +1,17 @@ +# Generated by Django 2.0.7 on 2018-09-12 22:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0014_auto_20180912_2054'), + ] + + operations = [ + migrations.RemoveField( + model_name='digitalobject', + name='fairsharing', + ), + ] diff --git a/FAIRshakeAPI/migrations/0005_auto_20180917_1713.py b/FAIRshakeAPI/migrations/0016_auto_20180917_1713.py similarity index 88% rename from FAIRshakeAPI/migrations/0005_auto_20180917_1713.py rename to FAIRshakeAPI/migrations/0016_auto_20180917_1713.py index 24be657..9fc44a9 100644 --- a/FAIRshakeAPI/migrations/0005_auto_20180917_1713.py +++ b/FAIRshakeAPI/migrations/0016_auto_20180917_1713.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ('FAIRshakeAPI', '0004_auto_20180823_1910'), + ('FAIRshakeAPI', '0015_remove_digitalobject_fairsharing'), ] operations = [ diff --git a/FAIRshakeAPI/migrations/0017_auto_20180917_1936.py b/FAIRshakeAPI/migrations/0017_auto_20180917_1936.py new file mode 100644 index 0000000..2d58171 --- /dev/null +++ b/FAIRshakeAPI/migrations/0017_auto_20180917_1936.py @@ -0,0 +1,21 @@ +# Generated by Django 2.0.7 on 2018-09-17 19:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('FAIRshakeAPI', '0016_auto_20180917_1713'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='answer', + unique_together={('assessment', 'metric')}, + ), + migrations.AlterUniqueTogether( + name='assessment', + unique_together={('project', 'target', 'rubric', 'methodology', 'assessor')}, + ), + ] diff --git a/FAIRshakeAPI/models.py b/FAIRshakeAPI/models.py index 2e6aefb..74bb726 100644 --- a/FAIRshakeAPI/models.py +++ b/FAIRshakeAPI/models.py @@ -1,16 +1,19 @@ +import uuid import logging from django.db import models from django.contrib.auth.models import AbstractUser from collections import OrderedDict +from extensions.versions_ex.models import VersionableEx as Versionable, VersionedManyToManyField +from extensions.versions_ex.fields import CustomUUIDField -class IdentifiableModelMixin(models.Model): - id = models.AutoField(primary_key=True) - +class IdentifiableModelMixin(Versionable): title = models.CharField(max_length=255, blank=False) - url = models.CharField(max_length=255, blank=True, null=False, default='') + url = models.CharField(max_length=255, blank=True) + # urls = ArrayField(models.CharField(max_length=255), blank=True) description = models.TextField(blank=True, null=False, default='') image = models.CharField(max_length=255, blank=True, null=False, default='') - tags = models.CharField(max_length=255, blank=True, null=False, default='') + tags = models.CharField(max_length=255, blank=True) + # tags = ArrayField(models.CharField(max_length=255), blank=True) type = models.CharField(max_length=16, blank=True, null=False, default='', choices=( ('', 'Other'), @@ -21,14 +24,19 @@ class IdentifiableModelMixin(models.Model): ('tool', 'Tool'), )) - authors = models.ManyToManyField('Author', blank=True) + slug = models.CharField(max_length=255, unique=True, blank=False, null=False) + + authors = VersionedManyToManyField('Author', blank=True) + + def urls_as_list(self): + return self.url.splitlines() def urls_as_list(self): return self.url.splitlines() def tags_as_list(self): return self.tags.split() - + def model_name(self): return self._meta.verbose_name_raw @@ -55,7 +63,7 @@ def has_permission(self, user, perm): else: logging.warning('perm %s not handled' % (perm)) return user.is_staff - + def __str__(self): return '{title} ({id})'.format(id=self.id, title=self.title) @@ -63,7 +71,7 @@ class Meta: abstract = True class Project(IdentifiableModelMixin): - digital_objects = models.ManyToManyField('DigitalObject', blank=True, related_name='projects') + digital_objects = VersionedManyToManyField('DigitalObject', blank=True, related_name='projects') class Meta: verbose_name = 'project' @@ -76,17 +84,12 @@ class MetaEx: ] class DigitalObject(IdentifiableModelMixin): - # A digital object's title is optional while its url is mandator, unlike the rest of the identifiables - title = models.CharField(max_length=255, blank=True, null=False, default='') + # A digital object's title is optional while its url is mandatory, unlike the rest of the identifiables url = models.CharField(max_length=255, blank=False) - fairsharing = models.CharField(max_length=255, blank=True, null=False, default='') - - rubrics = models.ManyToManyField('Rubric', blank=True, related_name='digital_objects') + # urls = ArrayField(models.CharField(max_length=255), blank=False) + title = models.CharField(max_length=255, blank=True, null=False, default='') - def attrs(self): - return dict(super().attrs(), **{ - 'fairsharing': self.fairsharing, - }) + rubrics = VersionedManyToManyField('Rubric', blank=True, related_name='digital_objects') class Meta: verbose_name = 'digital_object' @@ -102,7 +105,7 @@ class MetaEx: class Rubric(IdentifiableModelMixin): license = models.CharField(max_length=255, blank=True, null=False, default='') - metrics = models.ManyToManyField('Metric', blank=True, related_name='rubrics') + metrics = VersionedManyToManyField('Metric', blank=True, related_name='rubrics') class Meta: verbose_name = 'rubric' @@ -144,19 +147,17 @@ class MetaEx: 'rubrics', ] -class Assessment(models.Model): - id = models.AutoField(primary_key=True) - project = models.ForeignKey('Project', on_delete=models.SET_NULL, blank=True, null=True, related_name='assessments') - target = models.ForeignKey('DigitalObject', on_delete=models.CASCADE, related_name='assessments') - rubric = models.ForeignKey('Rubric', on_delete=models.CASCADE, related_name='assessments') +class Assessment(Versionable): + project = models.ForeignKey('Project', on_delete=models.SET_NULL, editable=False, blank=True, null=True, related_name='assessments') + target = models.ForeignKey('DigitalObject', on_delete=models.CASCADE, editable=False, related_name='assessments') + rubric = models.ForeignKey('Rubric', on_delete=models.CASCADE, editable=False, related_name='assessments') methodology = models.TextField(max_length=16, blank=True, choices=( ('self', 'Digital Object Creator Assessment'), ('user', 'Independent User Assessment'), ('auto', 'Automatic Assessment'), ('test', 'Test Assessment'), )) - assessor = models.ForeignKey('Author', on_delete=models.SET_NULL, related_name='+', blank=True, null=True) - timestamp = models.DateTimeField(auto_now_add=True) + assessor = models.ForeignKey('Author', on_delete=models.SET_NULL, editable=False, blank=True, null=True, related_name='+') def has_permission(self, user, perm): if perm in ['list', 'create', 'add', 'modify', 'remove', 'delete', 'retrieve']: @@ -181,16 +182,22 @@ class Meta: verbose_name = 'assessment' verbose_name_plural = 'assessments' ordering = ['id'] + unique_together = ( + 'project', + 'target', + 'rubric', + 'methodology', + 'assessor', + ) class MetaEx: children = [ 'answers', ] -class Answer(models.Model): - id = models.AutoField(primary_key=True) - assessment = models.ForeignKey('Assessment', on_delete=models.CASCADE, related_name='answers') - metric = models.ForeignKey('Metric', on_delete=models.CASCADE, related_name='answers') +class Answer(Versionable): + assessment = models.ForeignKey('Assessment', on_delete=models.CASCADE, editable=False, related_name='answers') + metric = models.ForeignKey('Metric', on_delete=models.CASCADE, editable=False, related_name='answers') answer = models.TextField(blank=True, null=False, default='') comment = models.TextField(blank=True, null=False, default='') url_comment = models.TextField(blank=True, null=False, default='') @@ -230,9 +237,13 @@ class Meta: verbose_name = 'answer' verbose_name_plural = 'answers' ordering = ['id'] + unique_together = ( + 'assessment', + 'metric', + ) class AssessmentRequest(models.Model): - id = models.AutoField(primary_key=True) + id = CustomUUIDField(primary_key=True, default=uuid.uuid4) assessment = models.OneToOneField('Assessment', on_delete=models.CASCADE, related_name='request') requestor = models.ForeignKey('Author', on_delete=models.SET_NULL, related_name='+', blank=True, null=True, default='') timestamp = models.DateTimeField(auto_now_add=True) diff --git a/FAIRshakeAPI/search.py b/FAIRshakeAPI/search.py index 102dc33..5383504 100644 --- a/FAIRshakeAPI/search.py +++ b/FAIRshakeAPI/search.py @@ -4,7 +4,7 @@ class SearchVector: def __init__(self, qs=None): - self.queryset = qs or self.get_model().objects.all() + self.queryset = qs or self.get_model().objects.current.all() def get_model(self): return self.model @@ -31,7 +31,6 @@ class IdentifiableSearchVector(SearchVector): lambda q: Q(description__icontains=q), lambda q: Q(tags__icontains=q), lambda q: Q(type__icontains=q), - lambda q: Q(authors__first_name__icontains=q), ] class ProjectSearchVector(IdentifiableSearchVector): diff --git a/FAIRshakeAPI/serializers.py b/FAIRshakeAPI/serializers.py index dc8a44d..954b3be 100644 --- a/FAIRshakeAPI/serializers.py +++ b/FAIRshakeAPI/serializers.py @@ -20,6 +20,7 @@ def update(self, instance, validated_data): class Meta: abstract = True + lookup_field = 'slug' read_only_fields = ( 'id', @@ -55,9 +56,9 @@ class Meta: ) class AssessmentSerializer(serializers.ModelSerializer): - project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.all()) - target = serializers.PrimaryKeyRelatedField(queryset=models.DigitalObject.objects.all()) - rubric = serializers.PrimaryKeyRelatedField(queryset=models.Rubric.objects.all()) + project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.current.all()) + target = serializers.PrimaryKeyRelatedField(queryset=models.DigitalObject.objects.current.all()) + rubric = serializers.PrimaryKeyRelatedField(queryset=models.Rubric.objects.current.all()) answers = AnswerSerializer(many=True) @@ -79,9 +80,9 @@ class Meta: ) class AssessmentResponseSerializer(serializers.ModelSerializer): - project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.all()) - target = serializers.PrimaryKeyRelatedField(queryset=models.DigitalObject.objects.all()) - rubric = serializers.PrimaryKeyRelatedField(queryset=models.Rubric.objects.all()) + project = serializers.PrimaryKeyRelatedField(queryset=models.Project.objects.current.all()) + target = serializers.PrimaryKeyRelatedField(queryset=models.DigitalObject.objects.current.all()) + rubric = serializers.PrimaryKeyRelatedField(queryset=models.Rubric.objects.current.all()) class Meta: model = models.Assessment diff --git a/FAIRshakeAPI/stats.py b/FAIRshakeAPI/stats.py index aae95a1..3ed47da 100644 --- a/FAIRshakeAPI/stats.py +++ b/FAIRshakeAPI/stats.py @@ -35,12 +35,12 @@ def RubricPieChart(assessments_with_rubric): rubrics=list(rubrics_dict.keys()) evals=list(rubrics_dict.values()) evals = [x/9 for x in evals] - rubric_names=[models.Rubric.objects.filter(id=x).values_list('title', flat=True).get() for x in rubrics] + rubric_names=[models.Rubric.objects.current.filter(id=x).values_list('title', flat=True).get() for x in rubrics] fig = [go.Pie(labels=rubric_names, values=evals, hoverinfo='label+value+percent', textinfo='percent')] yield _iplot(fig) def RubricsInProjectsOverlay(answers_within_project,projectid): - rubrics=np.unique(models.Rubric.objects.filter(assessments__project__id__in=[projectid]).values_list('id',flat=True)) + rubrics=np.unique(models.Rubric.objects.current.filter(assessments__project__id__in=[projectid]).values_list('id',flat=True)) all_trace=[] for rubric in rubrics: responses=answers_within_project.filter(assessment__rubric__id=rubric).values_list('answer',flat=True) @@ -50,7 +50,7 @@ def RubricsInProjectsOverlay(answers_within_project,projectid): if x not in scores_dict.keys(): scores_dict[x]=0 trace = go.Bar(x=['no (0)', 'no but (0.25)','yes but (0.75)','yes (1)'],y=[scores_dict['no'],scores_dict['nobut'],scores_dict['yesbut'],scores_dict['yes']], - name= models.Rubric.objects.filter(id=rubric).values_list('title', flat=True).get()) + name= models.Rubric.objects.current.filter(id=rubric).values_list('title', flat=True).get()) all_trace.append(trace) layout = {'xaxis': {'title': 'Answer'}, 'yaxis': {'title': 'Responses'}, @@ -108,47 +108,47 @@ def _DigitalObjectBarGraph(scores_dict): def DigitalObjectBarBreakdown(project): object_score_dict={} - for obj in project.digital_objects.all(): - answers=list(models.Answer.objects.filter(assessment__target__id=obj.id).values_list("answer",flat=True)) + for obj in project.digital_objects.current.all(): + answers=list(models.Answer.objects.current.filter(assessment__target__id=obj.id).values_list("answer",flat=True)) if len(answers)>0: scores=Scoring(answers) mean_score=np.mean(scores) - object_score_dict[models.DigitalObject.objects.filter(id=obj.id).values_list('title', flat=True).get()]=mean_score + object_score_dict[models.DigitalObject.objects.current.filter(id=obj.id).values_list('title', flat=True).get()]=mean_score return _DigitalObjectBarGraph(object_score_dict) # Overall scores for a particular rubric, project, metric...(***Can be placed on each rubric, project, and metric page***) # Get all scores in the database for a particular rubric, project, or metric # Input: Query Set (all answers), type of paramter, parameter ID -# example input query: models.Answer.objects.filter(assessment__project__id=11).all() +# example input query: models.Answer.objects.current.filter(assessment__project__id=11).all() def SingleQuery(querySet, PARAM, ID): if PARAM=="project": - title=models.Project.objects.filter(id=ID).values_list('title', flat=True).get() + title=models.Project.objects.current.filter(id=ID).values_list('title', flat=True).get() responses=querySet.values_list('answer', flat=True) scores=Scoring(responses) if len(scores)!=0: - print("Overall FAIR Evaluations for the project:",models.Project.objects.filter(id=ID).values_list('title', flat=True).get(),"(project id:",ID,")","\n") + print("Overall FAIR Evaluations for the project:",models.Project.objects.current.filter(id=ID).values_list('title', flat=True).get(),"(project id:",ID,")","\n") print("Mean FAIR score:",round(np.mean(scores),2)) print("Median FAIR score:",np.median(scores)) print("Total Assessments:",len(scores)/9) print("Total Questions Answered:",len(scores)) return BarGraphs(scores) if PARAM=="rubric": - title=models.Rubric.objects.filter(id=ID).values_list('title', flat=True).get() + title=models.Rubric.objects.current.filter(id=ID).values_list('title', flat=True).get() responses=querySet.values_list('answer', flat=True) scores=Scoring(responses) if len(scores)!=0: - print("Overall FAIR Evaluations for the rubric:",models.Rubric.objects.filter(id=ID).values_list('title', flat=True).get(),"(rubric id:",ID,")","\n") + print("Overall FAIR Evaluations for the rubric:",models.Rubric.objects.current.filter(id=ID).values_list('title', flat=True).get(),"(rubric id:",ID,")","\n") print("Mean FAIR score:",round(np.mean(scores),2)) print("Median FAIR score:",np.median(scores)) print("Total Assessments:",len(scores)/9) print("Total Questions Answered:",len(scores)) return BarGraphs(scores) if PARAM=="metric": - title=models.Metric.objects.filter(id=ID).values_list('title', flat=True).get() + title=models.Metric.objects.current.filter(id=ID).values_list('title', flat=True).get() responses=querySet.values_list('answer', flat=True) scores=Scoring(responses) if len(scores)!=0: - print("Overall FAIR Evaluations for the metric:",models.Metric.objects.filter(id=ID).values_list('title', flat=True).get(),"(metric id:",ID,")","\n") + print("Overall FAIR Evaluations for the metric:",models.Metric.objects.current.filter(id=ID).values_list('title', flat=True).get(),"(metric id:",ID,")","\n") print("Mean FAIR score:",round(np.mean(scores),2)) print("Median FAIR score:",np.median(scores)) print("Total Assessments:",len(scores)/9) @@ -159,24 +159,24 @@ def TablePlot(project): from django.template import Template, Context metrics = [ metric.title - for obj in project.digital_objects.all() + for obj in project.digital_objects.current.all() for assessment in obj.assessments.all() for metric in assessment.rubric.metrics.all() ] objs = [ obj.title - for obj in project.digital_objects.all() + for obj in project.digital_objects.current.all() ] scores = [ [ np.mean([ answer.value() - for answer in models.Answer.objects.filter(metric=metric, assessment__target=obj) + for answer in models.Answer.objects.current.filter(metric=metric, assessment__target=obj) ]) for assessment in obj.assessments.all() for metric in assessment.rubric.metrics.all() ] - for obj in project.digital_objects.all() + for obj in project.digital_objects.current.all() ] trace = go.Heatmap(z=scores, x=metrics, y=objs) data = [trace] diff --git a/FAIRshakeAPI/tests.py b/FAIRshakeAPI/tests.py index 0e46c1d..18a72e7 100644 --- a/FAIRshakeAPI/tests.py +++ b/FAIRshakeAPI/tests.py @@ -12,30 +12,36 @@ def setUp(self): models.Metric.objects.create( title='yesnobut test', type='yesnobut', + slug='yesnobut', ), models.Metric.objects.create( title='text test', type='text', + slug='text', ), models.Metric.objects.create( title='url test', type='url', + slug='url', ), ] for metric in metrics: metric.authors.add(user) rubric = models.Rubric.objects.create( title='rubric test', + slug='test', ) rubric.authors.add(user) for metric in metrics: rubric.metrics.add(metric) obj = models.DigitalObject.objects.create( url='https://fairshake.cloud/', + slug='fairshake', ) obj.rubrics.add(rubric) project = models.Project.objects.create( title='project test', + slug='test', ) project.authors.add(user) project.digital_objects.add(obj) @@ -153,7 +159,7 @@ def test_score_viewset_list(self): def test_project_viewset_detail(self): response = self.anonymous_client.get( reverse('project-detail', kwargs=dict( - pk=models.Project.objects.first().pk + slug=models.Project.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -162,7 +168,7 @@ def test_project_viewset_detail(self): response = self.anonymous_client.get( reverse('project-detail', kwargs=dict( - pk=models.Project.objects.first().pk + slug=models.Project.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -171,7 +177,7 @@ def test_project_viewset_detail(self): response = self.authenticated_client.get( reverse('project-detail', kwargs=dict( - pk=models.Project.objects.first().pk + slug=models.Project.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -180,7 +186,7 @@ def test_project_viewset_detail(self): response = self.authenticated_client.get( reverse('project-detail', kwargs=dict( - pk=models.Project.objects.first().pk + slug=models.Project.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -190,7 +196,7 @@ def test_project_viewset_detail(self): def test_digital_object_viewset_detail(self): response = self.anonymous_client.get( reverse('digital_object-detail', kwargs=dict( - pk=models.DigitalObject.objects.first().pk + slug=models.DigitalObject.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -199,7 +205,7 @@ def test_digital_object_viewset_detail(self): response = self.anonymous_client.get( reverse('digital_object-detail', kwargs=dict( - pk=models.DigitalObject.objects.first().pk + slug=models.DigitalObject.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -208,7 +214,7 @@ def test_digital_object_viewset_detail(self): response = self.authenticated_client.get( reverse('digital_object-detail', kwargs=dict( - pk=models.DigitalObject.objects.first().pk + slug=models.DigitalObject.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -217,7 +223,7 @@ def test_digital_object_viewset_detail(self): response = self.authenticated_client.get( reverse('digital_object-detail', kwargs=dict( - pk=models.DigitalObject.objects.first().pk + slug=models.DigitalObject.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -227,7 +233,7 @@ def test_digital_object_viewset_detail(self): def test_rubric_viewset_detail(self): response = self.anonymous_client.get( reverse('rubric-detail', kwargs=dict( - pk=models.Rubric.objects.first().pk + slug=models.Rubric.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -236,7 +242,7 @@ def test_rubric_viewset_detail(self): response = self.anonymous_client.get( reverse('rubric-detail', kwargs=dict( - pk=models.Rubric.objects.first().pk + slug=models.Rubric.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -245,7 +251,7 @@ def test_rubric_viewset_detail(self): response = self.authenticated_client.get( reverse('rubric-detail', kwargs=dict( - pk=models.Rubric.objects.first().pk + slug=models.Rubric.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -254,7 +260,7 @@ def test_rubric_viewset_detail(self): response = self.authenticated_client.get( reverse('rubric-detail', kwargs=dict( - pk=models.Rubric.objects.first().pk + slug=models.Rubric.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -264,7 +270,7 @@ def test_rubric_viewset_detail(self): def test_metric_viewset_detail(self): response = self.anonymous_client.get( reverse('metric-detail', kwargs=dict( - pk=models.Metric.objects.first().pk + slug=models.Metric.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -273,7 +279,7 @@ def test_metric_viewset_detail(self): response = self.anonymous_client.get( reverse('metric-detail', kwargs=dict( - pk=models.Metric.objects.first().pk + slug=models.Metric.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -282,7 +288,7 @@ def test_metric_viewset_detail(self): response = self.authenticated_client.get( reverse('metric-detail', kwargs=dict( - pk=models.Metric.objects.first().pk + slug=models.Metric.objects.current.first().slug )), HTTP_ACCEPT='text/html', ) @@ -291,7 +297,7 @@ def test_metric_viewset_detail(self): response = self.authenticated_client.get( reverse('metric-detail', kwargs=dict( - pk=models.Metric.objects.first().pk + slug=models.Metric.objects.current.first().slug )), HTTP_ACCEPT='application/json', ) @@ -301,25 +307,25 @@ def test_metric_viewset_detail(self): def test_assessment_viewset_detail(self): response = self.anonymous_client.get( reverse('assessment-detail', kwargs=dict( - pk=models.Assessment.objects.first().pk + pk=models.Assessment.objects.current.first().pk )), HTTP_ACCEPT='text/html', ) - self.assertEqual(response.status_code, 302, 'Login redirect expected') + self.assertEqual(response.status_code, 401) self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8', response) response = self.anonymous_client.get( reverse('assessment-detail', kwargs=dict( - pk=models.Assessment.objects.first().pk + pk=models.Assessment.objects.current.first().pk )), HTTP_ACCEPT='application/json', ) - self.assertEqual(response.status_code, 401, 'Permission denied expected') + self.assertEqual(response.status_code, 401) self.assertEqual(response['Content-Type'], 'application/json', response) response = self.authenticated_client.get( reverse('assessment-detail', kwargs=dict( - pk=models.Assessment.objects.first().pk + pk=models.Assessment.objects.current.first().pk )), HTTP_ACCEPT='text/html', ) @@ -328,7 +334,7 @@ def test_assessment_viewset_detail(self): response = self.authenticated_client.get( reverse('assessment-detail', kwargs=dict( - pk=models.Assessment.objects.first().pk + pk=models.Assessment.objects.current.first().pk )), HTTP_ACCEPT='application/json', ) diff --git a/FAIRshakeAPI/views.py b/FAIRshakeAPI/views.py index f940874..281bc3b 100644 --- a/FAIRshakeAPI/views.py +++ b/FAIRshakeAPI/views.py @@ -7,10 +7,9 @@ from django.conf import settings from django.core.cache import cache from django.db.models import Q -from django.forms import ModelChoiceField from django.core.exceptions import PermissionDenied +from django.forms import ModelChoiceField from rest_framework import views, viewsets, schemas, response, mixins, decorators, renderers, permissions -from functools import reduce def callback_or_redirect(request, *args, **kwargs): callback = request.GET.get('callback', None) @@ -22,9 +21,15 @@ def callback_or_redirect(request, *args, **kwargs): else: return shortcuts.redirect(callback) +def get_or_create(model, **kwargs): + try: + return model.objects.current.get(**kwargs) + except: + return model.objects.create(**kwargs) + class CustomTemplateHTMLRenderer(renderers.TemplateHTMLRenderer): def get_template_context(self, data, renderer_context): - context = super(CustomTemplateHTMLRenderer, self).get_template_context(data, renderer_context) or {} + context = super().get_template_context(data, renderer_context) or {} view = renderer_context['view'] request = view.request return view.get_template_context(request, context) @@ -42,11 +47,43 @@ def get_model(self): def get_model_name(self): return self.get_model()._meta.verbose_name_raw + + def get_queryset(self): + qs = getattr(self, 'queryset', None) + return self.get_model().objects.current.all() if qs is None else qs + def filter_queryset(self, qs): + ''' Ensure all resulting filter sets are distinct ''' + return super().filter_queryset(qs).order_by(*self.get_model()._meta.ordering).distinct() + + def get_template_names(self): + return ['fairshake/generic/page.html'] + + def get_template_context(self, request, context): + return getattr( + self, + 'get_%s_template_context' % (self.action), + getattr( + self, + 'get_%s_template_context' % ('detail' if self.detail else 'list'), + lambda request, context: context + ) + )( + request, + dict( + context, + model=self.get_model_name(), + action=self.action, + ), + ) + +class IdentifiableModelViewSet(CustomModelViewSet): + lookup_field = 'slug' + def get_model_children(self, obj): for child in self.get_model().MetaEx.children: child_attr = getattr(obj, child) - yield (child_attr.model._meta.verbose_name_raw, child_attr.all()) + yield (child_attr.model._meta.verbose_name_raw, child_attr.current.all()) def get_form(self): form_cls = self.form @@ -69,69 +106,11 @@ def save_form(self, request, form): instance = form.save() return instance - def get_queryset(self): - return getattr(self, 'queryset', self.get_model().objects.all()) - - def filter_queryset(self, qs): - ''' Ensure all resulting filter sets are distinct ''' - return super().filter_queryset(qs).order_by(*self.get_model()._meta.ordering).distinct() - - def get_template_names(self): - return ['fairshake/generic/page.html'] - - def get_detail_template_context(self, request, context): - paginator_cls = self.paginator.django_paginator_class - page_size = settings.REST_FRAMEWORK['VIEW_PAGE_SIZE'] - item = self.get_object() - form = self.get_form() - - return { - 'item': item, - 'form': form, - 'children': { - child: paginator_cls( - child_attr, - page_size, - ).get_page( - request.GET.get('page') - ) - for child, child_attr in self.get_model_children(item) - }, - } - - def get_list_template_context(self, request, context): - paginator_cls = self.paginator.django_paginator_class - page_size = settings.REST_FRAMEWORK['VIEW_PAGE_SIZE'] - form = self.get_form() - - return { - 'form': form, - 'items': paginator_cls( - self.filter_queryset( - self.get_queryset() - ), - page_size, - ).get_page( - request.GET.get('page') - ), - } - - def get_template_context(self, request, context): - return dict(context, - model=self.get_model_name(), - action=self.action, - **getattr(self, 'get_%s_template_context' % (self.action), - getattr(self, 'get_%s_template_context' % ('detail' if self.detail else 'list'), - lambda *args: args - ) - )(request, context), - ) - @decorators.action( detail=False, methods=['get', 'post'], renderer_classes=[CustomTemplateHTMLRenderer], ) - def add(self, request, pk=None, **kwargs): + def add(self, request, slug=None, **kwargs): self.check_permissions(request) if request.method == 'GET': return response.Response() @@ -140,7 +119,7 @@ def add(self, request, pk=None, **kwargs): if instance: return callback_or_redirect(request, self.get_model_name()+'-detail', - pk=instance.id, + slug=instance.slug, ) return response.Response() @@ -149,7 +128,7 @@ def add(self, request, pk=None, **kwargs): methods=['get', 'post'], renderer_classes=[CustomTemplateHTMLRenderer], ) - def modify(self, request, pk=None): + def modify(self, request, slug=None): item = self.get_object() if request.method == 'GET': return response.Response() @@ -158,7 +137,7 @@ def modify(self, request, pk=None): if instance: return callback_or_redirect(request, self.get_model_name()+'-detail', - pk=pk, + slug=instance.slug, ) return response.Response() @@ -166,7 +145,7 @@ def modify(self, request, pk=None): detail=True, methods=['get'], ) - def remove(self, request, pk=None): + def remove(self, request, slug=None): item = self.get_object() self.check_object_permissions(request, item) item.delete() @@ -174,19 +153,69 @@ def remove(self, request, pk=None): self.get_model_name()+'-list' ) -class DigitalObjectViewSet(CustomModelViewSet): + def get_add_template_context(self, request, context): + form = self.get_form() + return dict(context, **{ + 'form': form, + }) + + def get_modify_template_context(self, request, context): + item = self.get_object() + form = self.get_form() + + return dict(context, **{ + 'item': item, + 'form': form, + }) + + def get_retrieve_template_context(self, request, context): + paginator_cls = self.paginator.django_paginator_class + page_size = settings.REST_FRAMEWORK['VIEW_PAGE_SIZE'] + item = self.get_object() + + return dict(context, **{ + 'item': item, + 'children': { + child: paginator_cls( + child_attr, + page_size, + ).get_page( + request.GET.get('page') + ) + for child, child_attr in self.get_model_children(item) + }, + }) + + def get_list_template_context(self, request, context): + paginator_cls = self.paginator.django_paginator_class + page_size = settings.REST_FRAMEWORK['VIEW_PAGE_SIZE'] + form = self.get_form() + + return dict(context, **{ + 'form': form, + 'items': paginator_cls( + self.filter_queryset( + self.get_queryset() + ), + page_size, + ).get_page( + request.GET.get('page') + ), + }) + +class DigitalObjectViewSet(IdentifiableModelViewSet): model = models.DigitalObject form = forms.DigitalObjectForm serializer_class = serializers.DigitalObjectSerializer filter_class = filters.DigitalObjectFilterSet -class MetricViewSet(CustomModelViewSet): +class MetricViewSet(IdentifiableModelViewSet): model = models.Metric form = forms.MetricForm serializer_class = serializers.MetricSerializer filter_class = filters.MetricFilterSet -class ProjectViewSet(CustomModelViewSet): +class ProjectViewSet(IdentifiableModelViewSet): model = models.Project form = forms.ProjectForm serializer_class = serializers.ProjectSerializer @@ -197,7 +226,7 @@ class ProjectViewSet(CustomModelViewSet): methods=['get'], renderer_classes=[CustomTemplateHTMLRenderer], ) - def stats(self, request, pk=None): + def stats(self, request, slug=None): item = self.get_object() self.check_object_permissions(request, item) return response.Response() @@ -214,7 +243,7 @@ def get_stats_template_context(self, request, context): ] }) -class RubricViewSet(CustomModelViewSet): +class RubricViewSet(IdentifiableModelViewSet): model = models.Rubric form = forms.RubricForm serializer_class = serializers.RubricSerializer @@ -222,198 +251,151 @@ class RubricViewSet(CustomModelViewSet): class AssessmentViewSet(CustomModelViewSet): model = models.Assessment - form = forms.AssessmentForm serializer_class = serializers.AssessmentSerializer filter_classes = filters.AssessmentFilterSet def get_queryset(self): if self.request.user.is_anonymous: return models.Assessment.objects.none() - return models.Assessment.objects.filter( + return models.Assessment.objects.current.filter( Q(target__authors=self.request.user) | Q(project__authors=self.request.user) | Q(assessor=self.request.user) ) + + def get_objects(self, request): + target_id = request.GET.get('target', None) + rubric_id = request.GET.get('rubric', None) + project_id = request.GET.get('project', None) + + if target_id is None or rubric_id is None: + pass # TODO redirect to prepare + + if project_id: + assessment = get_or_create(models.Assessment, + project=models.Project.objects.get(id=project_id), + target=models.DigitalObject.objects.get(id=target_id), + rubric=models.Rubric.objects.get(id=rubric_id), + assessor=request.user, + methodology='user', + ) + else: + assessment = get_or_create(models.Assessment, + target=models.DigitalObject.objects.get(id=target_id), + rubric=models.Rubric.objects.get(id=rubric_id), + assessor=request.user, + methodology='user', + ) - def save_form(self, request, form): - assessment = form.save(commit=False) - assessment.assessor = request.user - assessment.methodology = 'user' - assessment.save() - if not assessment.answers.exists(): - for metric in assessment.rubric.metrics.all(): - answer = models.Answer( - assessment=assessment, - metric=metric, - ) - answer.save() + answers = [] + for metric in assessment.rubric.metrics.current.all(): + answer = get_or_create(models.Answer, + assessment=assessment, + metric=metric, + ) + answer_form = forms.AnswerForm( + dict(request.GET, **request.POST), + instance=answer, + prefix=answer.metric.id, + ) + answers.append({ + 'form': answer_form, + 'instance': answer, + }) - for answer in assessment.answers.all(): - answer_form = forms.AnswerForm( - request.POST, - instance=answer, - prefix=answer.metric.id, - ) - answer_form.save() + return { + 'assessment': assessment, + 'answers': answers, + } + + def save_form(self, request, form): + item = self.get_objects(request) + for answer in item.answers: + answer['form'].save() cache.delete_many([ ','.join(map('='.join, request.GET.items())), *map('='.join, request.GET.items()), ]) - return assessment - - def get_template_context(self, request, context): - if not self.get_model().has_permission(self.get_model(), request.user, self.action): - raise PermissionDenied + @decorators.action( + detail=False, methods=['get'], + renderer_classes=[CustomTemplateHTMLRenderer], + ) + def perform(self, request, **kwargs): + self.check_permissions(request) - if self.action in ['modify', 'retrieve']: - assessment = self.get_object() - assessment_form = forms.AssessmentForm(instance=assessment) + @decorators.action( + detail=False, methods=['get'], + renderer_classes=[CustomTemplateHTMLRenderer], + ) + def prepare(self, request, **kwargs): + self.check_permissions(request) - answers = [] - for answer in assessment.answers.all(): - answer_form = forms.AnswerForm( - prefix=answer.metric.id, - instance=answer, - ) - answers.append({ - 'form': answer_form, - 'instance': answer, - }) - - return dict(context, **{ - 'model': self.get_model_name(), - 'action': self.action, - 'form': assessment_form, - 'item': assessment, - 'answers': answers, - }) - elif self.action in ['add']: - assessment_form = forms.AssessmentForm(request.GET) - prepare = request.GET.get('prepare') - if not assessment_form.is_valid() or prepare is not None: - target = request.GET.get('target') - rubric = request.GET.get('rubric') - project = request.GET.get('project') - q = request.GET.get('q', '') - - # Prepare target queries - target_q = { - '__'.join(k.split('__')[1:]): v - for k, v in request.GET.items() - if k.split('__')[0] == 'target' - } - - # Strip protocol from url for search - target_url = target_q.get('url') - if target_url: - target_url = ''.join(target_url.split('://')[1:]) - target_q['url'] = target_url - - target_filters = [ - lambda q, _k=k+'__icontains', _v=v: Q(**{_k: _v}) - for k, v in target_q.items() - ] - - if target is not None: - targets = models.DigitalObject.objects.filter(id=target) - else: - if target_filters: - targets = models.DigitalObject.objects.filter( - reduce( - lambda F, f, q=q: (F|f(q)) if F is not None else f(q), - target_filters, - None, - ) - ).order_by('id').distinct() - else: - targets = None - - if not targets: - targets = search.DigitalObjectSearchVector().query(q) - - if rubric is not None: - rubrics = models.Rubric.objects.filter(id=rubric) - else: - rubrics = None - if target is not None: - rubrics = targets.first().rubrics.all() - if rubrics is None or not rubrics.exists(): - rubrics = models.Rubric.objects.all() - if rubrics.count() == 1: - rubric = rubrics.first().id - - if project is not None: - projects = models.Project.objects.filter(id=project) - else: - projects = None - if target is not None: - projects = targets.first().projects.all() - if projects is None or projects.exists(): - projects = models.Project.objects.all() - if projects.count() == 1: - project = projects.first().id - - if project is not None: - assessment_form = forms.AssessmentForm(dict(request.GET, **{ - 'target': targets.first().id, - 'rubric': rubrics.first().id, - 'project': projects.first().id, - })) - else: - assessment_form = forms.AssessmentForm(dict(request.GET, **{ - 'target': targets.first().id, - 'rubric': rubrics.first().id, - })) - - if prepare is not None or not assessment_form.is_valid(): - assessment_form.fields['target'] = ModelChoiceField(queryset=targets, required=True) - assessment_form.fields['rubric'] = ModelChoiceField(queryset=rubrics if rubrics.count() > 1 else models.Rubric.objects.all(), required=True) - assessment_form.fields['project'] = ModelChoiceField(queryset=projects if projects.count() > 1 else models.Project.objects.all(), required=False) - - return dict(context, **{ - 'model': self.get_model_name(), - 'action': 'prepare', - 'form': assessment_form, - }) - - assessment = assessment_form.save(commit=False) - assessment.assessor = request.user - - auto_assessment_results = Assessment.perform( - rubric=assessment.rubric, - target=assessment.target, - ) + def get_detail_template_context(self, request, context): + item = self.get_objects(request) + return dict(context, **item) - answers = [] - for metric in assessment.rubric.metrics.all(): - answer = models.Answer( - assessment=assessment, - metric=metric, - ) - answer_form = forms.AnswerForm( - dict(request.GET, **{ - '%s-%s' % (metric.id, key): attr - for key, attr in auto_assessment_results.get('metric:%d' % (metric.id), {}).items() - if attr - }), - prefix=metric.id, - instance=answer, - ) - answers.append({ - 'form': answer_form, - 'instance': answer, - }) - - return dict(context, **{ - 'model': self.get_model_name(), - 'action': self.action, - 'form': assessment_form, - 'item': assessment, - 'answers': answers, - }) - return super().get_template_context(request, context) + def get_list_template_context(self, request, context): + # List rubric objects + paginator_cls = self.paginator.django_paginator_class + page_size = settings.REST_FRAMEWORK['VIEW_PAGE_SIZE'] + + return dict(context, **{ + 'items': paginator_cls( + self.filter_queryset( + self.get_queryset() + ), + page_size, + ).get_page( + request.GET.get('page') + ), + }) + + def get_prepare_template_context(self, request, context): + if target is not None: + targets = models.DigitalObject.objects.filter(id=target) + else: + targets = search.DigitalObjectSearchVector().query(q) + + if rubric is not None: + rubrics = models.Rubric.objects.filter(id=rubric) + else: + rubrics = None + if target is not None: + rubrics = targets.first().rubrics.current.all() + if rubrics is None or not rubrics.exists(): + rubrics = models.Rubric.objects.current.all() + if rubrics.count() == 1: + rubric = rubrics.first().id + + if project is not None: + projects = models.Project.objects.filter(id=project) + else: + projects = None + if target is not None: + projects = targets.first().projects.current.all() + if projects is None or projects.exists(): + projects = models.Project.objects.current.all() + if projects.count() == 1: + project = projects.first().id + + if project is not None: + assessment_form = forms.AssessmentForm(dict(request.GET, **{ + 'target': targets.first().id, + 'rubric': rubrics.first().id, + 'project': projects.first().id, + })) + else: + assessment_form = forms.AssessmentForm(dict(request.GET, **{ + 'target': targets.first().id, + 'rubric': rubrics.first().id, + })) + + def get_template_context(self, request, context): + if not self.get_model().has_permission(self.get_model(), request.user, self.action): + raise PermissionDenied + return super.get_template_context(self, request, context) class AssessmentRequestViewSet(CustomModelViewSet): model = models.AssessmentRequest @@ -434,7 +416,7 @@ class ScoreViewSet( ): ''' Request an score for a digital resource ''' - queryset = models.Assessment.objects.all() + queryset = models.Assessment.objects.current.all() serializer_class = serializers.AssessmentSerializer filter_class = filters.ScoreFilterSet pagination_class = None @@ -487,7 +469,7 @@ def hist(self, request): if answers is None: answers = {} for assessment in self.filter_queryset(self.get_queryset()): - for answer in assessment.answers.all(): + for answer in assessment.answers.current.all(): value = answer.value() answers[value] = answers.get(value, 0) + 1 cache.set(key, answers, 60 * 60) diff --git a/FAIRshakeHub/templates/fairshake/assessment/add.html b/FAIRshakeHub/templates/fairshake/assessment/add.html index 87e7927..e141941 100644 --- a/FAIRshakeHub/templates/fairshake/assessment/add.html +++ b/FAIRshakeHub/templates/fairshake/assessment/add.html @@ -1,12 +1,18 @@ {% load bootstrap %} {% load filters %} -