Commit 4af2c003 by Christina Roberts

Merge pull request #22 from edx/christina/add-tags

Add tags to model.
parents d52887fd 3aac269b
Oleg Marshev <oleg@edx.org>
Tim Babych <tim.babych@gmail.com>
Christina Roberts <christina@edx.org>
# -*- coding: utf-8 -*-
""" Initial migration file for creating Note model """
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
""" Initial migration file for creating Note model """
dependencies = [
]
operations = [
migrations.CreateModel(
name='Note',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('user_id', models.CharField(
help_text=b'Anonymized user id, not course specific', max_length=255, db_index=True
)),
('course_id', models.CharField(max_length=255, db_index=True)),
('usage_id', models.CharField(help_text=b'ID of XBlock where the text comes from', max_length=255)),
('quote', models.TextField(default=b'')),
('text', models.TextField(default=b'', help_text=b"Student's thoughts on the quote", blank=True)),
('ranges', models.TextField(help_text=b'JSON, describes position of quote in the source text')),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
],
options={
},
bases=(models.Model,),
),
]
# -*- coding: utf-8 -*-
""" Add tags field to Note model """
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
""" Add tags field to Note model """
dependencies = [
('v1', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='note',
name='tags',
field=models.TextField(default=b'[]', help_text=b'JSON, list of comma-separated tags'),
preserve_default=True,
),
]
......@@ -15,6 +15,7 @@ class Note(models.Model):
ranges = models.TextField(help_text="JSON, describes position of quote in the source text")
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
tags = models.TextField(help_text="JSON, list of comma-separated tags", default="[]")
@classmethod
def create(cls, note_dict):
......@@ -34,6 +35,7 @@ class Note(models.Model):
note_dict['ranges'] = json.dumps(ranges)
note_dict['user_id'] = note_dict.pop('user', None)
note_dict['tags'] = json.dumps(note_dict.get('tags', list()))
return cls(**note_dict)
......@@ -43,7 +45,6 @@ class Note(models.Model):
"""
created = self.created.isoformat() if self.created else None
updated = self.updated.isoformat() if self.updated else None
return {
'id': str(self.pk),
'user': self.user_id,
......@@ -54,4 +55,5 @@ class Note(models.Model):
'ranges': json.loads(self.ranges),
'created': created,
'updated': updated,
'tags': json.loads(self.tags)
}
......@@ -20,6 +20,7 @@ class NoteTest(TestCase):
"endOffset": 10,
}
],
"tags": [u"apple", u"pear"],
}
def test_create_valid_note(self):
......@@ -47,3 +48,13 @@ class NoteTest(TestCase):
with self.assertRaises(ValidationError):
note = Note.create(payload)
note.full_clean()
def test_default_tags_value(self):
""" Test that a note without explicit tags has an empty list for the tags value """
payload = self.note_dict.copy()
payload.pop("tags")
note = Note.create(payload)
note.full_clean()
note.save()
self.assertEqual("[]", note.tags)
self.assertEqual([], note.as_dict()["tags"])
# -*- coding: utf-8 -*-
import jwt
import unittest
import ddt
from calendar import timegm
from datetime import datetime, timedelta
from mock import patch
......@@ -49,6 +50,7 @@ class BaseAnnotationViewTests(APITestCase):
"endOffset": 10,
}
],
"tags": ["pink", "lady"]
}
def _create_annotation(self, **kwargs):
......@@ -62,6 +64,20 @@ class BaseAnnotationViewTests(APITestCase):
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
return response.data.copy()
def _do_annotation_update(self, data, updated_fields):
"""
Helper method for updating an annotation.
Returns the response and the updated annotation.
"""
payload = self.payload.copy()
payload.update(updated_fields)
payload.update(self.headers)
url = reverse('api:v1:annotations_detail', kwargs={'annotation_id': data['id']})
response = self.client.put(url, payload, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
return response, self._get_annotation(data['id'])
def _get_annotation(self, annotation_id):
"""
Fetch annotation
......@@ -124,6 +140,16 @@ class AnnotationListViewTests(BaseAnnotationViewTests):
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['text'], '')
def test_create_no_tags(self):
"""
Ensure we can create a new note with empty list of tags.
"""
url = reverse('api:v1:annotations')
self.payload['tags'] = []
response = self.client.post(url, self.payload, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['tags'], [])
def test_create_ignore_created(self):
"""
Test if annotation 'created' field is not used by API.
......@@ -245,6 +271,7 @@ class AnnotationListViewTests(BaseAnnotationViewTests):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
@ddt.ddt
class AnnotationDetailViewTests(BaseAnnotationViewTests):
"""
Test one annotation updating, reading and deleting
......@@ -279,17 +306,29 @@ class AnnotationDetailViewTests(BaseAnnotationViewTests):
Ensure we can update an existing annotation.
"""
data = self._create_annotation(text=u"Foo")
payload = self.payload.copy()
payload.update({'id': data['id'], 'text': 'Bar'})
payload.update(self.headers)
url = reverse('api:v1:annotations_detail', kwargs={'annotation_id': data['id']})
response = self.client.put(url, payload, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
annotation = self._get_annotation(data['id'])
response, annotation = self._do_annotation_update(data, {'id': data['id'], 'text': 'Bar'})
self.assertEqual(annotation['text'], "Bar", "annotation wasn't updated in db")
self.assertEqual(response.data['text'], "Bar", "update annotation should be returned in response")
@ddt.data(
(["new", "tags"], ["new", "tags"]),
([], []),
(None, [u"pink", u"lady"]),
)
@ddt.unpack
def test_update_tags(self, updated_tags, expected_tags):
"""
Test that we can update tags on an existing annotation.
"""
data = self._create_annotation()
# If no tags are sent in the update, previously present tags should remain.
if updated_tags is None:
response, annotation = self._do_annotation_update(data, {'id': data['id']})
else:
response, annotation = self._do_annotation_update(data, {'id': data['id'], 'tags': updated_tags})
self.assertEqual(annotation["tags"], expected_tags)
self.assertEqual(response.data["tags"], expected_tags)
def test_update_fail(self):
"""
Ensure can not update an existing annotation with bad note.
......
......@@ -156,6 +156,7 @@ class AnnotationDetailView(APIView):
try:
note.text = self.request.data['text']
note.tags = json.dumps(self.request.data['tags'])
note.full_clean()
except KeyError as error:
log.debug(error, exc_info=True)
......
......@@ -49,7 +49,6 @@ INSTALLED_APPS = [
'rest_framework_swagger',
'corsheaders',
'haystack',
'notesapi',
'notesapi.v1',
]
......
......@@ -9,3 +9,4 @@ pep8==1.5.7
pylint==1.4.1
diff-cover==0.7.2
factory_boy==2.4.1
ddt==0.8.0
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment