Commit ab2f3a0c9530aa4f6960efc0709cc17f58dcee01

Authored by Matheus Lins
2 parents e9423551 0c543e36

Merge branch 'dev' of https://github.com/amadeusproject/amadeuslms into dev

amadeus/settings.py
... ... @@ -57,6 +57,7 @@ INSTALLED_APPS = [
57 57 'poll',
58 58 'links',
59 59 'exam',
  60 + 'files',
60 61  
61 62 ]
62 63  
... ...
courses/urls.py
... ... @@ -26,5 +26,8 @@ urlpatterns = [
26 26 url(r'^subjects/categories$',views.IndexSubjectCategoryView.as_view(), name='subject_category_index'),
27 27 url(r'^forum/', include('forum.urls', namespace = 'forum')),
28 28 url(r'^poll/', include('poll.urls', namespace = 'poll')),
  29 + url(r'^exam/', include('exam.urls', namespace = 'exam')),
  30 +
  31 +
29 32  
30 33 ]
... ...
exam/admin.py
1 1 from django.contrib import admin
2 2  
3   -# Register your models here.
  3 +from .models import Exam, Answer
  4 +
  5 +class ExamAdmin(admin.ModelAdmin):
  6 + list_display = ['name', 'slug','begin_date','limit_date']
  7 + search_fields = ['name','slug']
  8 +
  9 +class AnswerAdmin(admin.ModelAdmin):
  10 + list_display = ['answer','order']
  11 + search_fields = ['answer']
  12 +
  13 +admin.site.register(Exam, ExamAdmin)
  14 +admin.site.register(Answer, AnswerAdmin)
... ...
exam/forms.py
  1 +from django.utils.translation import ugettext_lazy as _
1 2 from django import forms
2 3 from .models import Exam
3 4  
4 5 class ExamForm(forms.ModelForm):
5 6 def clean_end_date(self):
6   - beginDate = self.data['beginDate']
7   - endDate = self.data['endDate']
  7 + begin_date = self.data['begin_date']
  8 + limit_date = self.data['limit_date']
8 9  
9   - if beginDate and endDate and endDate < beginDate:
  10 + if begin_date and limit_date and limit_date < begin_date:
10 11 raise forms.ValidationError(_('The end date may not be before the start date.'))
11 12 return endDate
12 13  
13   - def clean_begin_date(self):
14   - endDate = self.data['endDate']
15   - beginDate = self.data['beginDate']
16   -
17   - if enDate and benginDate and beginDate <= endDate:
18   - raise forms.ValidationError(_('The exam start date must be after the end of registration.'))
19   - return beginDate
20   -
21   - def clean_end_date(self):
22   - beginDate = self.data['beginDate']
23   - endDate = self.data['endDate']
24   -
25   - if beginDate and endDate and endDate < beginDate:
26   - raise forms.ValidationError(_('The finish date may not be before the start date.'))
27   - return end_date
28   -
29   -
30 14  
31 15 class Meta:
32 16 model = Exam
33   - fields = ['name','beginDate','endDate']
  17 + fields = ['name','begin_date','limit_date']
34 18  
35 19 widgets = {
36 20 'name': forms.TextInput(attrs={'placeholder': 'Exam?'}),
37   - 'beginDate': forms.DateTimeInput(attrs={'placeholder': 'Start date to resolve the exam'}),
38   - 'endDate': forms.DateTimeInput(attrs={'placeholder': 'Finish date permited to resolve the exam'}),
  21 + 'begin_date': forms.DateTimeInput(attrs={'placeholder': _('Start date to resolve the exam')}),
  22 + 'limit_date': forms.DateTimeInput(attrs={'placeholder': _('Finish date permited to resolve the exam')}),
39 23 }
... ...
exam/models.py
... ... @@ -5,23 +5,19 @@ from users.models import User
5 5 from core.models import Resource
6 6 from courses.models import Activity
7 7  
8   -
9   -
10   -class Exam(models.Model):
11   - name = models.CharField(_('Name'), max_length = 100)
12   - beginDate = models.DateTimeField(_('Start Date'), auto_now_add = True)
13   - endDate = models.DateTimeField(_('Date of last update'), auto_now=True)
  8 +class Exam(Activity):
  9 + begin_date = models.DateField(_('Begin of Course Date'))
14 10  
15 11 class Meta:
16   - #ordering = ('create_date','name')
17 12 verbose_name = _('Exam')
18 13 verbose_name_plural = _('Exams')
19 14  
20 15 def __str__(self):
21 16 return str(self.name) + str("/") + str(self.topic)
22 17  
  18 +
23 19 class Answer(models.Model):
24   - answer = models.CharField(_("Answer"), max_length = 200)
  20 + answer = models.CharField(_("Answer"), max_length = 300)
25 21 order = models.PositiveSmallIntegerField(_("Order"))
26 22 exam = models.ForeignKey(Exam, verbose_name = _('Answers'), related_name='answers')
27 23  
... ...
exam/permisissions.py
... ... @@ -1,12 +0,0 @@
1   -from rolepermissions.permissions import register_object_checker
2   -from amadeus.roles import SystemAdmin
3   -
4   -@register_object_checker()
5   -def edit_exam(role, user, exam):
6   - if (role == SystemAdmin):
7   - return True
8   -
9   - if (user in exam.topic.subject.professors.all()):
10   - return True
11   -
12   - return False
exam/permissions.py 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +from rolepermissions.permissions import register_object_checker
  2 +from amadeus.roles import SystemAdmin
  3 +
  4 +@register_object_checker()
  5 +def edit_exam(role, user, exam):
  6 + if (role == SystemAdmin):
  7 + return True
  8 +
  9 + if (user in exam.topic.subject.professors.all()):
  10 + return True
  11 +
  12 + return False
... ...
exam/templates/exam/create.html 0 → 100644
exam/templates/exam/form_exam.html
... ... @@ -1,60 +0,0 @@
1   -<div class="panel panel-info">
2   - <div class="panel-heading">
3   - <h3 class="panel-title">New Exam</h3>
4   - </div>
5   - <div class="panel-body">
6   - <form class="form-horizontal">
7   -
8   - <div class="form-group">
9   - <label for="examName" class="col-md-2 control-label">Exam Name</label>
10   -
11   - <div class="col-md-10">
12   - <input type="text" class="form-control" id="examName" placeholder="Exam Name">
13   - </div>
14   -
15   -
16   - </div>
17   - <div class="form-group">
18   - <label for="Date" class="col-md-2 control-label">Exam Period</label>
19   - <div class="col-md-5">
20   - <input type="date" class="form-control" id="beginDate" placeholder="Begin Date">
21   -
22   - </div>
23   - <div class="col-md-5">
24   - <input type="date" class="form-control" id="endDate" placeholder="End Date">
25   -
26   - </div>
27   - </div>
28   - <div class="form-group">
29   - <label for="deadline" class="col-md-2 control-label">Allow submissions after deadline?</label>
30   - <div class="col-md-10">
31   - <label>
32   - <input type="checkbox">
33   - </label>
34   - </div>
35   - </div>
36   - <div class="form-group">
37   - <label for="questionType" class="col-md-2 control-label">Question Type</label>
38   - <div class="col-md-10">
39   - <select id="questionType" class="form-control" onchange="showDiv (this)">
40   - <option selected disabled>Question Type</option>
41   - <option value="0">Multiple Choice</option>
42   - <option value="1">True or False</option>
43   - <option value="2">Gap Filling</option>
44   - <option value="3">Discursive Question</option>
45   - </select>
46   - </div>
47   - </div>
48   - </form>
49   - </div>
50   -</div>
51   -
52   -<script>
53   - function showDiv (elem) {
54   - if (elem.value == 0) {
55   - document.getElementById('multipleChoice').style.display = "block";
56   - } else if (elem.value == 1) {
57   - document.getElementById('trueOrFalse').style.display = "block";
58   - }
59   - }
60   -</script>
exam/templates/exam/remove.html 0 → 100644
exam/templates/exam/update.html 0 → 100644
exam/templates/exam/view.html 0 → 100644
exam/urls.py
... ... @@ -3,7 +3,8 @@ from django.conf.urls import url
3 3 from . import views
4 4  
5 5 urlpatterns = [
6   - url(r'^create/(?P<slug>[\w\-_]+)/$', views.CreateExam.as_view(), name='create_poll'),
7   - url(r'^update/(?P<slug>[\w\-_]+)/$', views.UpdateExam.as_view(), name='update_poll'),
8   -
  6 + url(r'^create/(?P<slug>[\w\-_]+)/$', views.CreateExam.as_view(), name='create_exam'),
  7 + url(r'^update/(?P<slug>[\w\-_]+)/$', views.UpdateExam.as_view(), name='update_exam'),
  8 + url(r'^view/(?P<slug>[\w\-_]+)/$', views.ViewExam.as_view(), name='view_exam'),
  9 + url(r'^delete/(?P<slug>[\w\-_]+)/$', views.DeleteExam.as_view(), name='delete_exam'),
9 10 ]
... ...
exam/views.py
... ... @@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse_lazy
8 8 from django.utils.translation import ugettext_lazy as _
9 9 from rolepermissions.verifications import has_role
10 10 from rolepermissions.verifications import has_object_permission
  11 +from django.db.models import Q
11 12 # from django.views.generic.edit import FormMixin
12 13  
13 14 from .forms import ExamForm
... ... @@ -16,34 +17,76 @@ from core.mixins import NotificationMixin
16 17 from users.models import User
17 18 from courses.models import Course, Topic
18 19  
19   -class CreateExam(LoginRequiredMixin,generic.CreateView):
  20 +class ViewExam(LoginRequiredMixin,generic.DetailView):
20 21  
21   - login_url = reverse_lazy("core:home")
22   - redirect_field_name = 'next'
23 22 model = Exam
24   - form_class = PollForm
25 23 context_object_name = 'exam'
26   - template_name = 'exam/form_exam.html'
27   - success_url = reverse_lazy('core:home')
  24 + template_name = 'exam/view.html'
  25 +
  26 + def get_object(self, queryset=None):
  27 + return get_object_or_404(Exam, slug = self.kwargs.get('slug'))
  28 +
  29 + def form_invalid(self, form,**kwargs):
  30 + context = super(ViewExam, self).form_invalid(form)
  31 + answers = {}
  32 + for key in self.request.POST:
  33 + if(key != 'csrfmiddlewaretoken' and key != 'name' and key!= 'begin_date' and key != 'limit_date' and key != 'all_students' and key != 'students'):
  34 + answers[key] = self.request.POST[key]
  35 +
  36 + keys = sorted(answers)
  37 + context.context_data['answers'] = answers
  38 + context.context_data['keys'] = keys
  39 + return context
28 40  
29 41 def form_valid(self, form):
30   - self.object = form.save(commit = False)
31   - topic = get_object_or_404(Topic, slug = self.kwargs.get('slug'))
32   - self.object.topic = topic
33   - self.object.save()
  42 + exam = self.object
  43 + exam = form.save(commit = False)
  44 + exam.answers.all().delete()
  45 + exam.save()
  46 +
34 47  
35 48 for key in self.request.POST:
36   - if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'beginDate' and key != 'endDate'):
37   - answer = Answer(answer=self.request.POST[key],order=key,poll=self.object)
  49 + if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'begin_date' and key != 'limit_date' and key != 'all_students' and key != 'students'):
  50 + answer = Answer(answer=self.request.POST[key],order=key,exam=exam)
38 51 answer.save()
39 52  
40   - return super(CreateExam, self).form_valid(form)
  53 + return super(ViewExam, self).form_valid(form)
  54 +
  55 + def get_context_data(self, **kwargs):
  56 + context = super(ViewExam, self).get_context_data(**kwargs)
  57 + exam = self.object
  58 + context['course'] = exam.topic.subject.course
  59 + context['subject'] = exam.topic.subject
  60 + context['subjects'] = exam.topic.subject.course.subjects.all()
  61 +
  62 + answers = {}
  63 + for answer in exam.answers.all():
  64 + answers[answer.order] = answer.answer
  65 +
  66 + keys = sorted(answers)
  67 + context['answers'] = answers
  68 + context['keys'] = keys
  69 +
  70 + print (context)
  71 + return context
  72 +
  73 +
  74 +class CreateExam(LoginRequiredMixin,HasRoleMixin,generic.CreateView):
  75 +
  76 + allowed_roles = ['professor', 'system_admin']
  77 + login_url = reverse_lazy("core:home")
  78 + redirect_field_name = 'next'
  79 + model = Exam
  80 + form_class = ExamForm
  81 + context_object_name = 'exam'
  82 + template_name = 'exam/create.html'
  83 + success_url = reverse_lazy('core:home')
41 84  
42 85 def form_invalid(self, form,**kwargs):
43 86 context = super(CreateExam, self).form_invalid(form)
44 87 answers = {}
45 88 for key in self.request.POST:
46   - if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'beginDate' and key != 'endDate'):
  89 + if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'begin_date' and key != 'limit_date' and key != 'all_students' and key != 'students'):
47 90 answers[key] = self.request.POST[key]
48 91  
49 92 keys = sorted(answers)
... ... @@ -51,47 +94,115 @@ class CreateExam(LoginRequiredMixin,generic.CreateView):
51 94 context.context_data['keys'] = keys
52 95 return context
53 96  
54   -class UpdateExam(LoginRequiredMixin,generic.UpdateView):
  97 + def form_valid(self, form):
  98 + self.object = form.save(commit = False)
  99 + topic = get_object_or_404(Topic, slug = self.kwargs.get('slug'))
  100 + self.object.topic = topic
  101 + self.object.save()
55 102  
  103 + for key in self.request.POST:
  104 + if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'begin_date' and key != 'limit_date' and key != 'all_students' and key != 'students'):
  105 + answer = Answer(answer=self.request.POST[key],order=key,exam=self.object)
  106 + answer.save()
  107 +
  108 + return super(CreatePoll, self).form_valid(form)
  109 +
  110 + def get_context_data(self, **kwargs):
  111 + context = super(CreateExam, self).get_context_data(**kwargs)
  112 + topic = get_object_or_404(Topic, slug = self.kwargs.get('slug'))
  113 + context['course'] = topic.subject.course
  114 + context['subject'] = topic.subject
  115 + context['subjects'] = topic.subject.course.subjects.all()
  116 + return context
  117 +
  118 +class UpdateExam(LoginRequiredMixin,HasRoleMixin,generic.UpdateView):
  119 +
  120 + allowed_roles = ['professor', 'system_admin']
56 121 login_url = reverse_lazy("core:home")
57 122 redirect_field_name = 'next'
58 123 model = Exam
59 124 form_class = ExamForm
60 125 context_object_name = 'exam'
61   - template_name = 'poll/form_exam.html'
  126 + template_name = 'exam/update.html'
62 127 success_url = reverse_lazy('core:home')
63 128  
64 129 def dispatch(self, *args, **kwargs):
65   - poll = get_object_or_404(Poll, slug = self.kwargs.get('slug'))
66   -
  130 + exam = get_object_or_404(Exam, slug = self.kwargs.get('slug'))
67 131 if(not has_object_permission('edit_exam', self.request.user, exam)):
68 132 return self.handle_no_permission()
69   - return super(UpdateExam, self).dispatch(*args, **kwargs)
  133 + return super(UpdatePoll, self).dispatch(*args, **kwargs)
70 134  
71 135 def get_object(self, queryset=None):
72   - return get_object_or_404(Poll, slug = self.kwargs.get('slug'))
  136 + return get_object_or_404(Exam, slug = self.kwargs.get('slug'))
  137 +
  138 + def form_invalid(self, form,**kwargs):
  139 + context = super(UpdateExam, self).form_invalid(form)
  140 + answers = {}
  141 + for key in self.request.POST:
  142 + if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'begin_date' and key != 'limit_date' and key != 'all_students' and key != 'students'):
  143 + answers[key] = self.request.POST[key]
  144 +
  145 + keys = sorted(answers)
  146 + context.context_data['answers'] = answers
  147 + context.context_data['keys'] = keys
  148 + return context
73 149  
74 150 def form_valid(self, form):
75   - poll = self.object
76   - poll = form.save(commit = False)
77   - poll.answers.all().delete()
78   - poll.save()
  151 + exam = self.object
  152 + exam = form.save(commit = False)
  153 + exam.answers.all().delete()
  154 + exam.save()
  155 +
79 156  
80 157 for key in self.request.POST:
81   - if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'beginDate' and key != 'endDate'):
  158 + if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'begin_date' and key != 'limit_date' and key != 'all_students' and key != 'students'):
82 159 answer = Answer(answer=self.request.POST[key],order=key,exam=exam)
83 160 answer.save()
84 161  
85 162 return super(UpdateExam, self).form_valid(form)
86 163  
87   - def form_invalid(self, form,**kwargs):
88   - context = super(UpdateExam, self).form_invalid(form)
  164 + def get_context_data(self, **kwargs):
  165 + context = super(UpdateExam, self).get_context_data(**kwargs)
  166 + exam = self.object
  167 + context['course'] = exam.topic.subject.course
  168 + context['subject'] = exam.topic.subject
  169 + context['subjects'] = exam.topic.subject.course.subjects.all()
  170 +
89 171 answers = {}
90   - for key in self.request.POST:
91   - if(key != 'csrfmiddlewaretoken' and key != 'name' and key != 'beginDate' and key != 'endDate'):
92   - answers[key] = self.request.POST[key]
  172 + for answer in exam.answers.all():
  173 + # print (key.answer)
  174 + answers[answer.order] = answer.answer
93 175  
94 176 keys = sorted(answers)
95   - context.context_data['answers'] = answers
96   - context.context_data['keys'] = keys
  177 + context['answers'] = answers
  178 + context['keys'] = keys
  179 +
  180 + return context
  181 +
  182 +class DeleteExam(LoginRequiredMixin, HasRoleMixin, generic.DeleteView):
  183 +
  184 + allowed_roles = ['professor', 'system_admin']
  185 + login_url = reverse_lazy("core:home")
  186 + redirect_field_name = 'next'
  187 + model = Exam
  188 + template_name = 'exam/remove.html'
  189 +
  190 + def dispatch(self, *args, **kwargs):
  191 + exam = get_object_or_404(Exam, slug = self.kwargs.get('slug'))
  192 + if(not has_object_permission('delete_exam', self.request.user, exam)):
  193 + return self.handle_no_permission()
  194 + return super(DeleteExam, self).dispatch(*args, **kwargs)
  195 +
  196 +
  197 + def get_context_data(self, **kwargs):
  198 + context = super(DeleteExam, self).get_context_data(**kwargs)
  199 + context['course'] = self.object.topic.subject.course
  200 + context['subject'] = self.object.topic.subject
  201 + context['exam'] = self.object
  202 + context['subjects'] = self.object.topic.subject.course.subjects.filter(Q(visible=True) | Q(professors__in=[self.request.user]))
  203 + if (has_role(self.request.user,'system_admin')):
  204 + context['subjects'] = self.object.topic.subject.course.subjects.all()
97 205 return context
  206 +
  207 + def get_success_url(self):
  208 + return reverse_lazy('course:view_topic', kwargs={'slug' : self.object.topic.slug})
... ...
files/migrations/0001_initial.py 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +# -*- coding: utf-8 -*-
  2 +# Generated by Django 1.10 on 2016-10-13 16:12
  3 +from __future__ import unicode_literals
  4 +
  5 +from django.db import migrations, models
  6 +import django.db.models.deletion
  7 +import files.models
  8 +
  9 +
  10 +class Migration(migrations.Migration):
  11 +
  12 + initial = True
  13 +
  14 + dependencies = [
  15 + ('core', '0002_mymetype'),
  16 + ('courses', '0004_auto_20161011_1951'),
  17 + ]
  18 +
  19 + operations = [
  20 + migrations.CreateModel(
  21 + name='TopicFile',
  22 + fields=[
  23 + ('material_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='courses.Material')),
  24 + ('description', models.TextField(blank=True, verbose_name='Description')),
  25 + ('file_url', models.FileField(upload_to=files.models.file_path, verbose_name='File')),
  26 + ('file_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topic_files', to='core.MymeType', verbose_name='Type file')),
  27 + ],
  28 + options={
  29 + 'verbose_name': 'File',
  30 + 'verbose_name_plural': 'Files',
  31 + },
  32 + bases=('courses.material',),
  33 + ),
  34 + ]
... ...
files/models.py
1 1 from django.db import models
2 2 from django.utils.translation import ugettext_lazy as _
3   -
4   -from courses.models import Activity
  3 +from core.models import MymeType
  4 +from courses.models import Material
5 5  
6 6 """
7 7 Function to return the path where the file should be saved
... ... @@ -14,8 +14,11 @@ def file_path(instance, filename):
14 14 It's one kind of activity available for a Topic.
15 15 It's like a support material for the students.
16 16 """
17   -class TopicFiles(Activity):
  17 +class TopicFile(Material):
  18 + description = models.TextField(_('Description'), blank=True)
18 19 file_url = models.FileField(verbose_name = _("File"), upload_to = file_path)
  20 + file_type = models.ForeignKey(MymeType, verbose_name=_('Type file'), related_name='topic_files')
  21 +
19 22  
20 23 class Meta:
21 24 verbose_name = _("File")
... ...
links/tests.py
1 1 from django.test import TestCase,Client
2 2 from django.core.urlresolvers import reverse
3 3 from rolepermissions.shortcuts import assign_role
  4 +from django.utils.translation import ugettext_lazy as _
4 5  
5 6 from users.models import User
6 7 from .models import *
  8 +from .forms import *
7 9  
8 10 # Create your tests here.
9 11 class LinkTestCase(TestCase):
... ... @@ -28,8 +30,10 @@ class LinkTestCase(TestCase):
28 30 "link" : 'teste.com'
29 31 }
30 32 response = self.client.post(url, data)
  33 + #self.assertEqual(response.status_code, 200)
  34 + self.assertFormError(response,'form',"link",_("Please enter a valid URL"))
31 35 self.assertEqual(Link.objects.all().count(),links+1) #After creating one link, if OK, the link was created successfully.
32   - self.assertEqual(response.status_code, 200)
  36 + self.assertEqual(response.status_code, 302) #If OK, User is getting redirected correctly.
33 37 self.assertTemplateUsed(template_name = 'links/link_modal.html')
34 38 # def test_update_link():
35 39 # pass
... ... @@ -41,8 +45,11 @@ class LinkTestCase(TestCase):
41 45 )
42 46 self.client.login(username='user', password = 'testing')
43 47 links = Link.objects.all().count()
  48 + deletedlink = Link.objects.get(name = self.link.name)
44 49 url = reverse('course:delete_link',kwargs={'linkname': self.link.name})
45 50 self.assertEqual(Link.objects.all().count(),links)
46   - response = self.client.get(url)
47   - self.assertEqual(Link.objects.all().count(),links - 1)
48   - self.assertEqual(response.status_code, 200)
  51 + response = self.client.post(url)
  52 + self.assertEqual(Link.objects.all().count(),links - 1) #Objeto removido
  53 + self.assertEqual(Link.objects.filter(name= deletedlink.name).exists(),False) #Objeto removido e sua não-existência verificada
  54 + #self.assertEqual(Link.objects.filter(name= deletedlink.name).exists(),True) #Objeto removido e sua existência verificada, se ERRO, objeto foi removido com sucesso!
  55 + self.assertEqual(response.status_code, 302) #If OK, User is getting redirected correctly.
... ...
links/views.py
... ... @@ -12,7 +12,7 @@ from .forms import *
12 12 class CreateLink(generic.CreateView):
13 13 template_name = 'links/link_modal.html'
14 14 form_class = CreateLinkForm
15   - success_url = reverse_lazy()
  15 + success_url = reverse_lazy('course:manage')
16 16 context_object_name = 'links'
17 17  
18 18 def form_valid(self, form):
... ... @@ -29,7 +29,7 @@ def deleteLink(request,linkname):
29 29 link = get_object_or_404(Link,name = linkname)
30 30 link.delete()
31 31 messages.success(request,_("Link deleted Successfully!"))
32   - return redirect('course:update_topic')
  32 + return redirect('course:manage')
33 33 class UpdateLink(generic.UpdateView):
34 34 template_name = 'links/'
35 35 form_class = UpdateLinkForm
... ...