Commit 2958a1827ea8e98f73b9ba9edff502bdf62d9398

Authored by Andrew Buss
1 parent ea351c696e
Exists in master

cleaned up some docstrings; compacted views

Showing 7 changed files with 418 additions and 82 deletions Side-by-side Diff

flashcards/api.py View file @ 2958a18
... ... @@ -3,6 +3,8 @@
3 3 from rest_framework.permissions import BasePermission
4 4  
5 5  
  6 +mock_no_params = lambda x:None
  7 +
6 8 class StandardResultsSetPagination(PageNumberPagination):
7 9 page_size = 40
8 10 page_size_query_param = 'page_size'
flashcards/migrations/0001_squashed_0015_auto_20150518_0017.py View file @ 2958a18
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +import django.contrib.auth.models
  6 +import django.utils.timezone
  7 +from django.conf import settings
  8 +import django.core.validators
  9 +import simple_email_confirmation.models
  10 +import flashcards.models
  11 +import flashcards.fields
  12 +
  13 +
  14 +class Migration(migrations.Migration):
  15 +
  16 + replaces = [(b'flashcards', '0001_initial'), (b'flashcards', '0002_auto_20150504_1327'), (b'flashcards', '0003_auto_20150504_1600'), (b'flashcards', '0004_auto_20150506_1443'), (b'flashcards', '0005_auto_20150510_1458'), (b'flashcards', '0006_auto_20150512_0042'), (b'flashcards', '0007_userflashcard_mask'), (b'flashcards', '0008_section_department_abbreviation'), (b'flashcards', '0009_auto_20150512_0318'), (b'flashcards', '0010_auto_20150513_1546'), (b'flashcards', '0011_auto_20150514_0207'), (b'flashcards', '0012_auto_20150516_0313'), (b'flashcards', '0013_auto_20150517_0402'), (b'flashcards', '0013_auto_20150516_2356'), (b'flashcards', '0014_merge'), (b'flashcards', '0015_auto_20150518_0017')]
  17 +
  18 + dependencies = [
  19 + ('auth', '__latest__'),
  20 + ]
  21 +
  22 + operations = [
  23 + migrations.CreateModel(
  24 + name='User',
  25 + fields=[
  26 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  27 + ('password', models.CharField(max_length=128, verbose_name='password')),
  28 + ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
  29 + ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
  30 + ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, verbose_name='username')),
  31 + ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
  32 + ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
  33 + ('email', models.EmailField(unique=True, max_length=254, verbose_name='email address', blank=True)),
  34 + ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
  35 + ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
  36 + ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
  37 + ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to=b'auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', verbose_name='groups')),
  38 + ],
  39 + options={
  40 + 'abstract': False,
  41 + 'verbose_name': 'user',
  42 + 'verbose_name_plural': 'users',
  43 + },
  44 + bases=(models.Model, simple_email_confirmation.models.SimpleEmailConfirmationUserMixin),
  45 + managers=[
  46 + ('objects', django.contrib.auth.models.UserManager()),
  47 + ],
  48 + ),
  49 + migrations.CreateModel(
  50 + name='Flashcard',
  51 + fields=[
  52 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  53 + ('text', models.CharField(help_text=b'The text on the card', max_length=255)),
  54 + ('pushed', models.DateTimeField(help_text=b'When the card was first pushed', auto_now_add=True)),
  55 + ('material_date', models.DateTimeField(help_text=b'The date with which the card is associated')),
  56 + ('is_hidden', models.BooleanField(default=False)),
  57 + ('hide_reason', models.CharField(help_text=b'Reason for hiding this card', max_length=255, blank=True)),
  58 + ('author', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
  59 + ],
  60 + options={
  61 + 'ordering': ['-pushed'],
  62 + },
  63 + ),
  64 + migrations.CreateModel(
  65 + name='FlashcardMask',
  66 + fields=[
  67 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  68 + ('ranges', models.CharField(max_length=255)),
  69 + ],
  70 + ),
  71 + migrations.CreateModel(
  72 + name='FlashcardReport',
  73 + fields=[
  74 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  75 + ('reason', models.CharField(max_length=255)),
  76 + ('flashcard', models.ForeignKey(to='flashcards.Flashcard')),
  77 + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
  78 + ],
  79 + ),
  80 + migrations.CreateModel(
  81 + name='LecturePeriod',
  82 + fields=[
  83 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  84 + ('week_day', models.IntegerField(help_text=b'0-indexed day of week, starting at Monday')),
  85 + ('start_time', models.TimeField()),
  86 + ('end_time', models.TimeField()),
  87 + ],
  88 + ),
  89 + migrations.CreateModel(
  90 + name='Section',
  91 + fields=[
  92 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  93 + ('department', models.CharField(max_length=50)),
  94 + ('course_num', models.CharField(max_length=6)),
  95 + ('course_title', models.CharField(max_length=50)),
  96 + ('instructor', models.CharField(max_length=50)),
  97 + ('quarter', models.CharField(max_length=4)),
  98 + ('whitelist', models.ManyToManyField(related_name='whitelisted_sections', to=settings.AUTH_USER_MODEL)),
  99 + ],
  100 + options={
  101 + 'ordering': ['-quarter'],
  102 + },
  103 + ),
  104 + migrations.CreateModel(
  105 + name='UserFlashcard',
  106 + fields=[
  107 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  108 + ('pulled', models.DateTimeField(help_text=b'When the user pulled the card', null=True, blank=True)),
  109 + ('unpulled', models.DateTimeField(help_text=b'When the user unpulled this card', null=True, blank=True)),
  110 + ('flashcard', models.ForeignKey(to='flashcards.Flashcard')),
  111 + ('mask', models.ForeignKey(help_text=b"A mask which overrides the card's mask", to='flashcards.FlashcardMask')),
  112 + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
  113 + ],
  114 + options={
  115 + 'ordering': ['-pulled'],
  116 + },
  117 + ),
  118 + migrations.CreateModel(
  119 + name='WhitelistedAddress',
  120 + fields=[
  121 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  122 + ('email', models.EmailField(max_length=254)),
  123 + ('section', models.ForeignKey(related_name='whitelist', to='flashcards.Section')),
  124 + ],
  125 + ),
  126 + migrations.AddField(
  127 + model_name='lectureperiod',
  128 + name='section',
  129 + field=models.ForeignKey(to='flashcards.Section'),
  130 + ),
  131 + migrations.AddField(
  132 + model_name='flashcard',
  133 + name='previous',
  134 + field=models.ForeignKey(blank=True, to='flashcards.Flashcard', help_text=b'The previous version of this card, if one exists', null=True),
  135 + ),
  136 + migrations.AddField(
  137 + model_name='flashcard',
  138 + name='section',
  139 + field=models.ForeignKey(help_text=b'The section with which the card is associated', to='flashcards.Section'),
  140 + ),
  141 + migrations.AddField(
  142 + model_name='user',
  143 + name='sections',
  144 + field=models.ManyToManyField(to=b'flashcards.Section'),
  145 + ),
  146 + migrations.AddField(
  147 + model_name='user',
  148 + name='user_permissions',
  149 + field=models.ManyToManyField(related_query_name='user', related_name='user_set', to=b'auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions'),
  150 + ),
  151 + migrations.AlterUniqueTogether(
  152 + name='userflashcard',
  153 + unique_together=set([('user', 'flashcard')]),
  154 + ),
  155 + migrations.AlterIndexTogether(
  156 + name='userflashcard',
  157 + index_together=set([('user', 'flashcard')]),
  158 + ),
  159 + migrations.AlterUniqueTogether(
  160 + name='section',
  161 + unique_together=set([('department', 'course_num', 'quarter', 'instructor')]),
  162 + ),
  163 + migrations.AlterUniqueTogether(
  164 + name='lectureperiod',
  165 + unique_together=set([('section', 'start_time', 'week_day')]),
  166 + ),
  167 + migrations.AlterUniqueTogether(
  168 + name='flashcardreport',
  169 + unique_together=set([('user', 'flashcard')]),
  170 + ),
  171 + migrations.AlterModelOptions(
  172 + name='section',
  173 + options={},
  174 + ),
  175 + migrations.AlterModelManagers(
  176 + name='user',
  177 + managers=[
  178 + ('objects', flashcards.models.EmailOnlyUserManager()),
  179 + ],
  180 + ),
  181 + migrations.AlterField(
  182 + model_name='flashcardreport',
  183 + name='reason',
  184 + field=models.CharField(max_length=255, blank=True),
  185 + ),
  186 + migrations.AlterField(
  187 + model_name='lectureperiod',
  188 + name='week_day',
  189 + field=models.IntegerField(help_text=b'1-indexed day of week, starting at Sunday'),
  190 + ),
  191 + migrations.AlterField(
  192 + model_name='user',
  193 + name='sections',
  194 + field=models.ManyToManyField(help_text=b'The sections which the user is enrolled in', to=b'flashcards.Section'),
  195 + ),
  196 + migrations.AlterField(
  197 + model_name='user',
  198 + name='username',
  199 + field=models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, error_messages={'unique': 'A user with that username already exists.'}, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')]),
  200 + ),
  201 + migrations.AlterField(
  202 + model_name='section',
  203 + name='instructor',
  204 + field=models.CharField(max_length=100),
  205 + ),
  206 + migrations.AlterField(
  207 + model_name='userflashcard',
  208 + name='mask',
  209 + field=models.ForeignKey(blank=True, to='flashcards.FlashcardMask', help_text=b"A mask which overrides the card's mask", null=True),
  210 + ),
  211 + migrations.CreateModel(
  212 + name='UserFlashcardQuiz',
  213 + fields=[
  214 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  215 + ('when', models.DateTimeField(auto_now=True)),
  216 + ('blanked_word', models.CharField(help_text=b'The character range which was blanked', max_length=8, blank=True)),
  217 + ('response', models.CharField(help_text=b"The user's response", max_length=255, null=True, blank=True)),
  218 + ('correct', models.NullBooleanField(help_text=b"The user's self-evaluation of their response")),
  219 + ('user_flashcard', models.ForeignKey(to='flashcards.UserFlashcard')),
  220 + ],
  221 + ),
  222 + migrations.AlterModelOptions(
  223 + name='section',
  224 + options={'ordering': ['-course_title']},
  225 + ),
  226 + migrations.AlterUniqueTogether(
  227 + name='section',
  228 + unique_together=set([]),
  229 + ),
  230 + migrations.RemoveField(
  231 + model_name='section',
  232 + name='whitelist',
  233 + ),
  234 + migrations.RemoveField(
  235 + model_name='userflashcard',
  236 + name='mask',
  237 + ),
  238 + migrations.DeleteModel(
  239 + name='FlashcardMask',
  240 + ),
  241 + migrations.AddField(
  242 + model_name='flashcard',
  243 + name='mask',
  244 + field=flashcards.fields.MaskField(blank_sep=b',', range_sep=b'-', max_length=255, blank=True, help_text=b'The mask on the card', null=True),
  245 + ),
  246 + migrations.AddField(
  247 + model_name='userflashcard',
  248 + name='mask',
  249 + field=flashcards.fields.MaskField(default=None, blank_sep=b',', range_sep=b'-', max_length=255, blank=True, help_text=b'The user-specific mask on the card', null=True),
  250 + ),
  251 + migrations.AddField(
  252 + model_name='section',
  253 + name='department_abbreviation',
  254 + field=models.CharField(max_length=10, db_index=True),
  255 + ),
  256 + migrations.AlterField(
  257 + model_name='section',
  258 + name='course_num',
  259 + field=models.CharField(max_length=6, db_index=True),
  260 + ),
  261 + migrations.AlterField(
  262 + model_name='section',
  263 + name='course_title',
  264 + field=models.CharField(max_length=50, db_index=True),
  265 + ),
  266 + migrations.AlterField(
  267 + model_name='section',
  268 + name='department',
  269 + field=models.CharField(max_length=50, db_index=True),
  270 + ),
  271 + migrations.AlterField(
  272 + model_name='section',
  273 + name='instructor',
  274 + field=models.CharField(max_length=100, db_index=True),
  275 + ),
  276 + migrations.AlterField(
  277 + model_name='section',
  278 + name='quarter',
  279 + field=models.CharField(max_length=4, db_index=True),
  280 + ),
  281 + migrations.AlterField(
  282 + model_name='flashcard',
  283 + name='material_date',
  284 + field=models.DateTimeField(default=django.utils.timezone.now, help_text=b'The date with which the card is associated'),
  285 + ),
  286 + migrations.AlterModelOptions(
  287 + name='lectureperiod',
  288 + options={'ordering': ['section', 'week_day']},
  289 + ),
  290 + migrations.AlterField(
  291 + model_name='userflashcard',
  292 + name='pulled',
  293 + field=models.DateTimeField(default=None, help_text=b'When the user pulled the card', null=True, blank=True),
  294 + ),
  295 + migrations.RemoveField(
  296 + model_name='userflashcard',
  297 + name='unpulled',
  298 + ),
  299 + migrations.CreateModel(
  300 + name='FlashcardHide',
  301 + fields=[
  302 + ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
  303 + ('reason', models.CharField(max_length=255, null=True, blank=True)),
  304 + ('hidden', models.DateTimeField(auto_now_add=True)),
  305 + ('flashcard', models.ForeignKey(to='flashcards.Flashcard')),
  306 + ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
  307 + ],
  308 + ),
  309 + migrations.AlterUniqueTogether(
  310 + name='flashcardreport',
  311 + unique_together=set([]),
  312 + ),
  313 + migrations.RemoveField(
  314 + model_name='flashcardreport',
  315 + name='flashcard',
  316 + ),
  317 + migrations.RemoveField(
  318 + model_name='flashcardreport',
  319 + name='user',
  320 + ),
  321 + migrations.DeleteModel(
  322 + name='FlashcardReport',
  323 + ),
  324 + migrations.AlterUniqueTogether(
  325 + name='flashcardhide',
  326 + unique_together=set([('user', 'flashcard')]),
  327 + ),
  328 + migrations.AlterIndexTogether(
  329 + name='flashcardhide',
  330 + index_together=set([('user', 'flashcard')]),
  331 + ),
  332 + migrations.AlterField(
  333 + model_name='flashcard',
  334 + name='hide_reason',
  335 + field=models.CharField(default=None, help_text=b'Reason for hiding this card', max_length=255, blank=True),
  336 + ),
  337 + migrations.AlterField(
  338 + model_name='flashcard',
  339 + name='previous',
  340 + field=models.ForeignKey(default=None, blank=True, to='flashcards.Flashcard', help_text=b'The previous version of this card, if one exists', null=True),
  341 + ),
  342 + migrations.AlterField(
  343 + model_name='flashcard',
  344 + name='hide_reason',
  345 + field=models.CharField(default=None, max_length=255, null=True, help_text=b'Reason for hiding this card', blank=True),
  346 + ),
  347 + migrations.AlterField(
  348 + model_name='flashcard',
  349 + name='previous',
  350 + field=models.ForeignKey(default=None, blank=True, to='flashcards.Flashcard', help_text=b'The previous version of this card, if one exists', null=True),
  351 + ),
  352 + migrations.AlterField(
  353 + model_name='flashcard',
  354 + name='hide_reason',
  355 + field=models.CharField(default=b'', max_length=255, null=True, help_text=b'Reason for hiding this card', blank=True),
  356 + ),
  357 + ]
flashcards/migrations/0015_auto_20150518_0017.py View file @ 2958a18
  1 +# -*- coding: utf-8 -*-
  2 +from __future__ import unicode_literals
  3 +
  4 +from django.db import models, migrations
  5 +
  6 +
  7 +class Migration(migrations.Migration):
  8 +
  9 + dependencies = [
  10 + ('flashcards', '0014_merge'),
  11 + ]
  12 +
  13 + operations = [
  14 + migrations.AlterField(
  15 + model_name='flashcard',
  16 + name='hide_reason',
  17 + field=models.CharField(default=b'', max_length=255, null=True, help_text=b'Reason for hiding this card', blank=True),
  18 + ),
  19 + ]
flashcards/models.py View file @ 2958a18
... ... @@ -12,7 +12,6 @@
12 12 AbstractUser._meta.get_field('email')._unique = True
13 13 AbstractUser._meta.get_field('username')._unique = False
14 14  
15   -
16 15 class EmailOnlyUserManager(UserManager):
17 16 """
18 17 A tiny extension of Django's UserManager which correctly creates users
19 18  
... ... @@ -143,10 +142,9 @@
143 142 if self.is_hidden or self.flashcardhide_set.filter(user=user).exists(): return True
144 143 return False
145 144  
146   -
147   - def hide_from(self, user):
  145 + def hide_from(self, user, reason=None):
148 146 if self.is_in_deck(user): user.unpull(self)
149   - obj, created = FlashcardHide.objects.get_or_create(user=user, flashcard=self)
  147 + obj, created = FlashcardHide.objects.get_or_create(user=user, flashcard=self, reason=None)
150 148 if not created:
151 149 raise ValidationError("The card has already been hidden.")
152 150 obj.save()
... ... @@ -308,6 +306,9 @@
308 306 def get_feed_for_user(self, user):
309 307 qs = Flashcard.objects.filter(section=self).exclude(userflashcard__user=user).order_by('pushed')
310 308 return qs
  309 +
  310 + def get_cards_for_user(self, user):
  311 + return Flashcard.cards_visible_to(user).filter(section=self)
311 312  
312 313 def __unicode__(self):
313 314 return '%s %s: %s (%s %s)' % (
flashcards/serializers.py View file @ 2958a18
  1 +from json import dumps, loads
  2 +
1 3 from django.utils.datetime_safe import datetime
2 4 from django.utils.timezone import now
3 5 import pytz
... ... @@ -8,7 +10,6 @@
8 10 from rest_framework.relations import HyperlinkedRelatedField
9 11 from rest_framework.serializers import ModelSerializer, Serializer
10 12 from rest_framework.validators import UniqueValidator
11   -from json import dumps, loads
12 13  
13 14  
14 15 class EmailSerializer(Serializer):
15 16  
... ... @@ -132,28 +133,11 @@
132 133 else:
133 134 raise serializers.ValidationError("Material date is outside allowed range for this quarter")
134 135  
135   - def validate_previous(self, value):
136   - if value is None:
137   - return value
138   - if Flashcard.objects.filter(pk=value.pk).count() > 0:
139   - return value
140   - raise serializers.ValidationError("Invalid previous Flashcard object")
141   -
142 136 def validate_pushed(self, value):
143 137 if value > datetime.now():
144 138 raise serializers.ValidationError("Invalid creation date for the Flashcard")
145 139 return value
146 140  
147   - def validate_text(self, value):
148   - if len(value) > 255:
149   - raise serializers.ValidationError("Flashcard text limit exceeded")
150   - return value
151   -
152   - def validate_hide_reason(self, value):
153   - if len(value) > 255:
154   - raise serializers.ValidationError("Hide reason limit exceeded")
155   - return value
156   -
157 141 def validate_mask(self, value):
158 142 if value is None:
159 143 return None
... ... @@ -163,7 +147,7 @@
163 147  
164 148 class Meta:
165 149 model = Flashcard
166   - exclude = 'author',
  150 + exclude = 'author', 'previous'
167 151  
168 152  
169 153 class FlashcardUpdateSerializer(serializers.Serializer):
flashcards/tests/test_api.py View file @ 2958a18
... ... @@ -207,7 +207,7 @@
207 207 self.inaccessible_flashcard.save()
208 208 self.flashcard = Flashcard(text="jason", section=self.section, author=self.user)
209 209 self.flashcard.save()
210   - self.flashcard.add_to_deck(self.user)
  210 + #self.flashcard.add_to_deck(self.user)
211 211 self.client.login(email='none@none.com', password='1234')
212 212  
213 213 def test_edit_flashcard(self):
... ... @@ -258,9 +258,6 @@
258 258 response = self.client.post('/api/flashcards/%d/hide/' % self.flashcard.id, format='json')
259 259 self.assertEqual(response.status_code, HTTP_204_NO_CONTENT)
260 260 self.assertTrue(self.flashcard.is_hidden_from(self.user))
261   -
262   - response = self.client.post('/api/flashcards/%d/hide/' % self.flashcard.id, format='json')
263   - self.assertContains(response, 'The card has already been hidden', status_code=400)
264 261  
265 262 response = self.client.post('/api/flashcards/%d/hide/' % self.inaccessible_flashcard.pk, format='json')
266 263 # This should fail because the user is not enrolled in section id 2
flashcards/views.py View file @ 2958a18
... ... @@ -9,7 +9,7 @@
9 9 FlashcardUpdateSerializer
10 10 from rest_framework.decorators import detail_route, permission_classes, api_view, list_route
11 11 from rest_framework.generics import ListAPIView, GenericAPIView
12   -from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, UpdateModelMixin
  12 +from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin
13 13 from rest_framework.permissions import IsAuthenticated
14 14 from rest_framework.viewsets import ReadOnlyModelViewSet, GenericViewSet
15 15 from django.core.mail import send_mail
... ... @@ -42,11 +42,7 @@
42 42 Add the current user to a specified section
43 43 If the class has a whitelist, but the user is not on the whitelist, the request will fail.
44 44 ---
45   - omit_serializer: true
46   - parameters:
47   - - fake: None
48   - parameters_strategy:
49   - form: replace
  45 + view_mocker: flashcards.api.mock_no_params
50 46 """
51 47  
52 48 self.get_object().enroll(request.user)
53 49  
54 50  
... ... @@ -58,22 +54,26 @@
58 54 Remove the current user from a specified section
59 55 If the user is not in the class, the request will fail.
60 56 ---
61   - omit_serializer: true
62   - parameters:
63   - - fake: None
64   - parameters_strategy:
65   - form: replace
  57 + view_mocker: flashcards.api.mock_no_params
66 58 """
67 59 try:
68 60 self.get_object().drop(request.user)
69   - except django.core.exceptions.PermissionDenied as e: raise PermissionDenied(e)
70   - except django.core.exceptions.ValidationError as e: raise ValidationError(e)
  61 + except django.core.exceptions.PermissionDenied as e:
  62 + raise PermissionDenied(e)
  63 + except django.core.exceptions.ValidationError as e:
  64 + raise ValidationError(e)
71 65 return Response(status=HTTP_204_NO_CONTENT)
72 66  
73 67 @list_route(methods=['GET'])
74 68 def search(self, request):
75 69 """
76 70 Returns a list of sections which match a user's query
  71 + ---
  72 + parameters:
  73 + - name: q
  74 + description: space-separated list of terms
  75 + required: true
  76 + type: form
77 77 """
78 78 query = request.GET.get('q', None)
79 79 if not query: return Response('[]')
... ... @@ -277,7 +277,7 @@
277 277 return Response(status=HTTP_204_NO_CONTENT)
278 278  
279 279  
280   -class FlashcardViewSet(GenericViewSet, UpdateModelMixin, CreateModelMixin, RetrieveModelMixin):
  280 +class FlashcardViewSet(GenericViewSet, CreateModelMixin, RetrieveModelMixin):
281 281 queryset = Flashcard.objects.all()
282 282 serializer_class = FlashcardSerializer
283 283 permission_classes = [IsAuthenticated, IsEnrolledInAssociatedSection]
... ... @@ -287,6 +287,8 @@
287 287 serializer = FlashcardSerializer(data=request.data)
288 288 serializer.is_valid(raise_exception=True)
289 289 data = serializer.validated_data
  290 + if not request.user.is_in_section(data['section']):
  291 + raise PermissionDenied('The user is not enrolled in that section')
290 292 data['author'] = request.user
291 293 flashcard = Flashcard.objects.create(**data)
292 294 self.perform_create(flashcard)
293 295  
294 296  
... ... @@ -294,33 +296,13 @@
294 296 response_data = FlashcardSerializer(flashcard)
295 297 return Response(response_data.data, status=HTTP_201_CREATED, headers=headers)
296 298  
297   - @detail_route(methods=['post'])
298   - def hide(self, request, pk):
299   - """
300   - Hide a flashcard
301   - ---
302   - omit_serializer: true
303   - parameters:
304   - - fake: None
305   - parameters_strategy:
306   - form: replace
307   - """
308   - try:
309   - self.get_object().hide_from(request.user)
310   - except django.core.exceptions.ValidationError:
311   - raise ValidationError("The card has already been hidden.")
312   - return Response(status=HTTP_204_NO_CONTENT)
313 299  
314 300 @detail_route(methods=['post'])
315 301 def unhide(self, request, pk):
316 302 """
317   - Report the given card
  303 + Unhide the given card
318 304 ---
319   - omit_serializer: true
320   - parameters:
321   - - fake: None
322   - parameters_strategy:
323   - form: replace
  305 + view_mocker: flashcards.api.mock_no_params
324 306 """
325 307 hide = get_object_or_404(FlashcardHide, user=request.user, flashcard=self.get_object())
326 308 hide.delete()
327 309  
328 310  
329 311  
330 312  
... ... @@ -329,27 +311,23 @@
329 311 @detail_route(methods=['post'])
330 312 def report(self, request, pk):
331 313 """
332   - Report the given card
  314 + Hide the given card
333 315 ---
334   - omit_serializer: true
335   - parameters:
336   - - fake: None
337   - parameters_strategy:
338   - form: replace
  316 + view_mocker: flashcards.api.mock_no_params
339 317 """
340 318 obj, created = FlashcardHide.objects.get_or_create(user=request.user, flashcard=self.get_object())
341   - obj.reason = request.data['reason']
342   - if created:
343   - obj.save()
  319 + obj.reason = request.data.get('reason', None)
  320 + obj.save()
344 321 return Response(status=HTTP_204_NO_CONTENT)
345 322  
  323 + hide = report
  324 +
346 325 @detail_route(methods=['POST'])
347 326 def pull(self, request, pk):
348 327 """
349 328 Pull a card from the live feed into the user's deck.
350   - :param request: The request object
351   - :param pk: The primary key
352   - :return: A 204 response upon success.
  329 + ---
  330 + view_mocker: flashcards.api.mock_no_params
353 331 """
354 332 user = request.user
355 333 flashcard = self.get_object()
356 334  
357 335  
... ... @@ -360,21 +338,19 @@
360 338 def unpull(self, request, pk):
361 339 """
362 340 Unpull a card from the user's deck
363   - :param request: The request object
364   - :param pk: The primary key
365   - :return: A 204 response upon success.
  341 + ---
  342 + view_mocker: flashcards.api.mock_no_params
366 343 """
367 344 user = request.user
368 345 flashcard = self.get_object()
369 346 user.unpull(flashcard)
370 347 return Response(status=HTTP_204_NO_CONTENT)
371 348  
372   - def update(self, request, *args, **kwargs):
  349 + def partial_update(self, request, *args, **kwargs):
373 350 """
374 351 Edit settings related to a card for the user.
375   - :param request: The request object.
376   - :param pk: The primary key of the flashcard.
377   - :return: A 204 response upon success.
  352 + ---
  353 + request_serializer: FlashcardUpdateSerializer
378 354 """
379 355 user = request.user
380 356 flashcard = self.get_object()