New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Annotation api #65
Annotation api #65
Changes from 12 commits
e979cff
6611aec
a78d2fb
59f81d1
d91ce99
890b18e
8fe7ed8
3352d3e
0c7825d
af5e9d5
16a970a
0b5c855
326e158
64a73ca
a229e54
85c2161
aa1d7a1
7753ed8
c60582c
423af03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# -*- coding: utf-8 -*- | ||
from __future__ import unicode_literals | ||
|
||
import os | ||
import binascii | ||
|
||
from django.db import migrations | ||
|
||
|
||
def forwards_func(apps, schema_editor): | ||
User = apps.get_model('core', 'User') | ||
Token = apps.get_model('authtoken', 'Token') | ||
for user in User.objects.all().iterator(): | ||
Token.objects.get_or_create( | ||
user=user, | ||
defaults=dict(key=binascii.hexlify(os.urandom(20)).decode())) | ||
|
||
def reverse_func(apps, schema_editor): | ||
pass | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
('core', '0003_statisticcache'), | ||
('authtoken', '0002_auto_20160226_1747'), | ||
] | ||
|
||
operations = [ | ||
migrations.RunPython(forwards_func, reverse_func), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
from funcy import all, compact, map | ||
|
||
from rest_framework import serializers | ||
|
||
from legacy.models import Platform, Series, Analysis, MetaAnalysis, PlatformProbe | ||
from tags.models import SerieAnnotation, Tag | ||
|
||
from .fields import S3Field | ||
|
||
|
||
|
@@ -41,12 +44,72 @@ def __init__(self, *args, **kwargs): | |
self.fields['specie'].required = True | ||
self.fields['specie'].allow_blank = False | ||
|
||
|
||
class SerieAnnotationSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = SerieAnnotation | ||
exclude = ['series_tag', 'created_on', 'modified_on', ] | ||
|
||
|
||
class SampleAnnotationValidator(serializers.Serializer): | ||
series = serializers.PrimaryKeyRelatedField(queryset=Series.objects.all()) | ||
tag = serializers.PrimaryKeyRelatedField(queryset=Tag.objects.all()) | ||
column = serializers.CharField(max_length=512, required=False, allow_blank=True, default='') | ||
regex = serializers.CharField(max_length=512, required=False, default='') | ||
comment = serializers.CharField(max_length=512, required=False, default='') | ||
values = serializers.JSONField() | ||
# values field is a json object with sample_id as keys and tag value as values | ||
|
||
def validate_values(self, value): | ||
if not all(isinstance(v, (unicode, str)) for v in value.values()): | ||
raise serializers.ValidationError( | ||
"Values should be a dict with serie tag id as a key and tag value as a value") | ||
|
||
if not all(isinstance(v, (unicode, str, int)) for v in value.keys()): | ||
raise serializers.ValidationError( | ||
"Values should be a dict with serie tag id as a key and tag value as a value") | ||
|
||
return value | ||
|
||
def validate(self, data): | ||
samples = data['series'].samples.all() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Samples are defined by serie and platform. |
||
|
||
gsm_to_id = {s.gsm_name.upper(): s.id for s in samples} | ||
id_to_gsm = {v: k for k, v in gsm_to_id.iteritems()} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove support for ids, only work with gsms. Do not play with case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is also |
||
|
||
try: | ||
values = { | ||
gsm_to_id[key.upper()] | ||
if isinstance(key, (unicode, str)) and key.upper().startswith('GSM') | ||
else | ||
int(key): value | ||
for key, value in data['values'].iteritems() | ||
} | ||
except KeyError as err: | ||
raise serializers.ValidationError( | ||
"There is samples with id {0} which doesn't belongs to series {1}" | ||
.format(err.message, data['series'].id)) | ||
except ValueError as err: | ||
raise serializers.ValidationError(unicode(err)) | ||
|
||
tagged_samples_ids = set(values.keys()) | ||
all_samples_ids = set(data['series'].samples.values_list('id', flat=True)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Totally unneeded query. Use samples from above. Or use |
||
missing_annotations = all_samples_ids - tagged_samples_ids | ||
extra_annotations = tagged_samples_ids - all_samples_ids | ||
|
||
if missing_annotations == extra_annotations == set(): | ||
data['values'] = values | ||
return data | ||
|
||
missing_text = "There are samples with ids {0} which are missing their annotation"\ | ||
.format(map(id_to_gsm, missing_annotations)) \ | ||
if missing_annotations else None | ||
extra_text = "There are samples with ids {0} which doesn't belongs to series {1}"\ | ||
.format(list(extra_annotations), data['series'].id) \ | ||
if extra_annotations else None | ||
|
||
raise serializers.ValidationError(compact([missing_text, extra_text])) | ||
|
||
class TagSerializer(serializers.ModelSerializer): | ||
class Meta: | ||
model = Tag | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,24 +2,28 @@ | |
|
||
from cacheops import cached_as | ||
from django.http import JsonResponse, HttpResponse | ||
from funcy import walk_keys | ||
from django.db import transaction | ||
from funcy import walk_keys, first | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return empty line. |
||
from pandas.computation.ops import UndefinedVariableError | ||
from rest_framework import viewsets, exceptions | ||
from rest_framework.decorators import list_route | ||
from rest_framework.permissions import AllowAny | ||
from rest_framework.permissions import AllowAny, IsAuthenticatedOrReadOnly | ||
from rest_framework.renderers import CoreJSONRenderer | ||
from rest_framework.response import Response | ||
from rest_framework.schemas import SchemaGenerator | ||
from rest_framework.serializers import ValidationError | ||
from rest_framework.views import APIView | ||
from rest_framework_swagger import renderers | ||
|
||
from tags.models import SerieAnnotation, Tag, SampleAnnotation | ||
from tags.models import SerieAnnotation, Tag, SampleAnnotation, SeriesTag | ||
from tags.misc import save_annotation, save_validation | ||
from legacy.models import Platform, Series, Analysis, MetaAnalysis, PlatformProbe | ||
from s3field.ops import frame_dumps | ||
from analysis.analysis import get_analysis_df | ||
from .serializers import (PlatformSerializer, SeriesSerializer, AnalysisSerializer, | ||
AnalysisParamSerializer, SerieAnnotationSerializer, | ||
TagSerializer, MetaAnalysisSerializer, PlatformProbeSerializer, | ||
SampleAnnotationValidator, | ||
) | ||
|
||
|
||
|
@@ -74,6 +78,7 @@ class SerieAnnotationViewSet(viewsets.ReadOnlyModelViewSet): | |
serializer_class = SerieAnnotationSerializer | ||
|
||
class SampleAnnotationViewSet(viewsets.ViewSet): | ||
permission_classes = [IsAuthenticatedOrReadOnly] | ||
KEYS = { | ||
'serie_annotation__tag__tag_name': 'tag_name', | ||
'serie_annotation__tag_id': 'tag_id', | ||
|
@@ -88,6 +93,35 @@ class SampleAnnotationViewSet(viewsets.ViewSet): | |
'annotation': 'annotation', | ||
} | ||
|
||
@transaction.atomic | ||
def create(self, request): | ||
user_id = request.user.id | ||
|
||
serializer = SampleAnnotationValidator( | ||
data=request.data) | ||
serializer.is_valid(raise_exception=True) | ||
|
||
series = serializer.validated_data['series'] | ||
tag = serializer.validated_data['tag'] | ||
|
||
series_tag = first(SeriesTag.objects.filter(series=series, tag=tag)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use Django's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also specify There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also MAJOR error not filtering by platform here. SeriesTag is defined by these 3 things: serie, platform, tag. And filter by |
||
|
||
if not series_tag: | ||
# create annotation | ||
save_annotation(user_id, serializer.validated_data, True) | ||
else: | ||
# create validation | ||
if series_tag.created_by.id == user_id: | ||
raise ValidationError( | ||
{'non_field_errors': ["You can't validate your own annotation"]}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't it non field error by default? |
||
|
||
res, err = save_validation(user_id, series_tag, serializer.validated_data, True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You get orphan ValidationJob here, clean it up. |
||
if not res: | ||
raise ValidationError( | ||
{'non_field_errors': [err]}) | ||
|
||
return Response(status=201) | ||
|
||
def list(self, request, format=None): | ||
""" | ||
Download all samples annotations. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove column and regex fields