diff --git a/common/__init__.py b/common/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/common/helpers/__init__.py b/common/helpers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a29cff832e8239e0ffc0cf1db98d51745c4c88c8 --- /dev/null +++ b/common/helpers/__init__.py @@ -0,0 +1,30 @@ +def model_form_data(model, form_class): + ''' + Returns a dict that can be used to instantiate a form object. + It fills in the model's data, but filters out fields that are not on the form. + Example: + + class Car(models.Model): + brand = CharField(max_length = 50) + fuel_tank_size = FloatField() + # more fields + + class CreateCarForm(forms.ModelForm): + fields = ['brand'] + + my_car = Car(brand='Nissan', fuel_tank_size=60) + + model_form_data(my_car, CreateCarForm) + # returns {'brand': 'Nissan'} + + Note that the returned dict does not have a field 'fuel_tank_size', because it is not + on the form. + ''' + + model_data = model.__dict__ + form_fields = list(form_class().fields.keys()) + return filter_keys(model_data, form_fields) + + +def filter_keys(dictionary, keys_to_keep): + return {key: dictionary[key] for key in keys_to_keep} diff --git a/scipost/factories.py b/scipost/factories.py index 80a4c045804aa2cf133823d882323c7d20013927..c69b975f60db3f7f1b5952012384a719ecc7c9bb 100644 --- a/scipost/factories.py +++ b/scipost/factories.py @@ -11,9 +11,9 @@ class ContributorFactory(factory.django.DjangoModelFactory): model = Contributor title = "MR" - user = factory.SubFactory(UserFactory, contributor=None) + user = factory.SubFactory('scipost.factories.UserFactory', contributor=None) status = 1 - vetted_by = factory.SubFactory(ContributorFactory) + vetted_by = factory.SubFactory('scipost.factories.ContributorFactory', vetted_by=None) class UserFactory(factory.django.DjangoModelFactory): diff --git a/theses/factories.py b/theses/factories.py index 19489c3c15babf86f0939a1a261c234fb7285560..1a12cd026941ad06ef7e4ad82c798b6947813a93 100644 --- a/theses/factories.py +++ b/theses/factories.py @@ -16,3 +16,4 @@ class ThesisLinkFactory(factory.django.DjangoModelFactory): institution = factory.Faker('company') defense_date = factory.Faker('date_time_this_century') abstract = factory.Faker('text') + domain = 'ET' diff --git a/theses/forms.py b/theses/forms.py index 2193a767c32084fbfdd4f5e7b34143408a68e58d..13b4a1793a47b46f2e9c993e8c55c2d86ebf0834 100644 --- a/theses/forms.py +++ b/theses/forms.py @@ -1,6 +1,7 @@ from django import forms from .models import * +from .helpers import past_years THESIS_ACTION_CHOICES = ( (0, 'modify'), @@ -21,12 +22,10 @@ class RequestThesisLinkForm(forms.ModelForm): fields = ['type', 'discipline', 'domain', 'subject_area', 'title', 'author', 'supervisor', 'institution', 'defense_date', 'pub_link', 'abstract'] - - def __init__(self, *args, **kwargs): - super(RequestThesisLinkForm, self).__init__(*args, **kwargs) - self.fields['defense_date'].widget.attrs.update({'placeholder': 'Format: YYYY-MM-DD'}) - self.fields['pub_link'].widget.attrs.update({'placeholder': 'Full URL'}) - self.fields['abstract'].widget.attrs.update({'cols': 100}) + widgets = { + 'defense_date': forms.SelectDateWidget(years=past_years(50)), + 'pub_link': forms.TextInput(attrs={'placeholder': 'Full URL'}) + } class VetThesisLinkForm(forms.Form): diff --git a/theses/helpers.py b/theses/helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..7b2961e45306298fd6544c8e515f7a8d22675579 --- /dev/null +++ b/theses/helpers.py @@ -0,0 +1,9 @@ +import datetime + + +def past_years(n): + ''' + Gives back list of integers representing a range of n years, counting down from current year. + ''' + this_year = datetime.datetime.now().year + return range(this_year, this_year - n, -1) diff --git a/theses/test_forms.py b/theses/test_forms.py index 2e9cb5f6ba351402af656aec1be5d9ac257bc5c0..ce07aa6214b9811d4b0ca54e09c7b5b304b93901 100644 --- a/theses/test_forms.py +++ b/theses/test_forms.py @@ -1 +1,26 @@ +import factory + from django.test import TestCase + +from .factories import ThesisLinkFactory +from .forms import RequestThesisLinkForm +from common.helpers import model_form_data + + +class TestRequestThesisLink(TestCase): + fixtures = ['permissions', 'groups'] + + def setUp(self): + self.valid_form_data = model_form_data(ThesisLinkFactory(), RequestThesisLinkForm) + + def test_valid_data_is_valid(self): + form_data = self.valid_form_data + form = RequestThesisLinkForm(self.valid_form_data) + self.assertTrue(form.is_valid()) + + def test_empty_domain_is_invalid(self): + form_data = self.valid_form_data + form_data['domain'] = '' + form = RequestThesisLinkForm(form_data) + form.is_valid() + self.assertEqual(form.errors['domain'], ['This field is required.']) diff --git a/theses/test_views.py b/theses/test_views.py index e1c9c7982e1cd36b83d213a82f3794ec860a05f5..7e70a6dd41170d221dfd8a4d586a4a8b0ff0c38c 100644 --- a/theses/test_views.py +++ b/theses/test_views.py @@ -1,10 +1,42 @@ -from django.test import TestCase +from django.test import TestCase, RequestFactory from django.test.client import Client +from django.contrib.auth.models import AnonymousUser +from django.urls import reverse + +from .views import RequestThesisLink +from scipost.factories import UserFactory +from .factories import ThesisLinkFactory +from .models import ThesisLink class TestThesisDetail(TestCase): + fixtures = ['groups', 'permissions'] - def test_acknowledges_after_submitting_comment(self): + def test_visits_valid_thesis_detail(self): + thesis_link = ThesisLinkFactory() client = Client() - response = client.post('/theses/1') - self.assertEqual(response.get('location'), 'bladiebla') + target = reverse('theses:thesis', kwargs={'thesislink_id': thesis_link.id}) + response = client.post(target) + self.assertEqual(response.status_code, 200) + + +class TestRequestThesisLink(TestCase): + fixtures = ['groups', 'permissions'] + + def setUp(self): + self.client = Client() + + def test_response_when_not_logged_in(self): + '''A visitor that is not logged in cannot view this page.''' + response = self.client.get(reverse('theses:request_thesislink')) + self.assertEqual(response.status_code, 403) + + def test_response_when_logged_in(self): + request = RequestFactory().get(reverse('theses:request_thesislink')) + request.user = UserFactory() + response = RequestThesisLink.as_view()(request) + self.assertEqual(response.status_code, 200) + + def test_redirects_to_acknowledgement_page(self): + response = self.client.post(reverse('theses:request_thesislink'), {}, follow=True) + self.assertRedirects(response, reverse('scipost:acknowledgement')) diff --git a/theses/urls.py b/theses/urls.py index 88bd434545a5636f9b09e832bbd7e24b91bf6d12..05839aa7550bedf319b58d82bb3e7aa59703cacf 100644 --- a/theses/urls.py +++ b/theses/urls.py @@ -8,7 +8,7 @@ urlpatterns = [ url(r'^$', views.theses, name='theses'), url(r'^browse/(?P<discipline>[a-z]+)/(?P<nrweeksback>[0-9]+)/$', views.browse, name='browse'), url(r'^(?P<thesislink_id>[0-9]+)/$', views.thesis_detail, name='thesis'), - url(r'^request_thesislink$', views.request_thesislink, name='request_thesislink'), + url(r'^request_thesislink$', views.RequestThesisLink.as_view(), name='request_thesislink'), url(r'^vet_thesislink_requests$', views.vet_thesislink_requests, name='vet_thesislink_requests'), url(r'^vet_thesislink_request_ack/(?P<thesislink_id>[0-9]+)$', diff --git a/theses/views.py b/theses/views.py index 7a2a2309d363eae341a18e0a62c4470c6f5b1cdf..7e738e39d719d57235c783350d1a1d2b4c6cb814 100644 --- a/theses/views.py +++ b/theses/views.py @@ -1,4 +1,5 @@ import datetime + from django.utils import timezone from django.shortcuts import get_object_or_404, render from django.contrib.auth import authenticate, login, logout @@ -9,6 +10,8 @@ from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect from django.views.decorators.csrf import csrf_protect from django.db.models import Avg +from django.views.generic.edit import CreateView +from django.utils.decorators import method_decorator from .models import * from .forms import * @@ -25,38 +28,20 @@ title_dict = dict(TITLE_CHOICES) # Convert titles for use in emails ################ -@permission_required('scipost.can_request_thesislinks', raise_exception=True) -def request_thesislink(request): - if request.method == 'POST': - form = RequestThesisLinkForm(request.POST) - if form.is_valid(): - contributor = Contributor.objects.get(user=request.user) - thesislink = ThesisLink( - requested_by=contributor, - type=form.cleaned_data['type'], - discipline=form.cleaned_data['discipline'], - domain=form.cleaned_data['domain'], - subject_area=form.cleaned_data['subject_area'], - title=form.cleaned_data['title'], - author=form.cleaned_data['author'], - supervisor=form.cleaned_data['supervisor'], - institution=form.cleaned_data['institution'], - defense_date=form.cleaned_data['defense_date'], - pub_link=form.cleaned_data['pub_link'], - abstract=form.cleaned_data['abstract'], - latest_activity=timezone.now(), - ) - thesislink.save() - # return HttpResponseRedirect('request_thesislink_ack') - context = {'ack_header': 'Thank you for your request for a Thesis Link', - 'ack_message': 'Your request will soon be handled by an Editor. ', - 'followup_message': 'Return to your ', - 'followup_link': reverse('scipost:personal_page'), - 'followup_link_label': 'personal page'} - return render(request, 'scipost/acknowledgement.html', context) - else: - form = RequestThesisLinkForm() - return render(request, 'theses/request_thesislink.html', {'form': form}) +@method_decorator(permission_required( + 'scipost.can_request_thesislinks', raise_exception=True), name='dispatch') +class RequestThesisLink(CreateView): + form_class = RequestThesisLinkForm + template_name = 'theses/request_thesislink.html' + success_url = '' + + def form_valid(self, form): + context = {'ack_header': 'Thank you for your request for a Thesis Link', + 'ack_message': 'Your request will soon be handled by an Editor. ', + 'followup_message': 'Return to your ', + 'followup_link': reverse('scipost:personal_page'), + 'followup_link_label': 'personal page'} + return render(self.request, 'scipost/acknowledgement.html', context) @permission_required('scipost.can_vet_thesislink_requests', raise_exception=True)