Skip to content

Commit 416849e

Browse files
committed
Merge branch 'master' into refactor-project-notifications-views
2 parents da029b8 + 307aad4 commit 416849e

File tree

9 files changed

+118
-130
lines changed

9 files changed

+118
-130
lines changed

readthedocs/api/v2/urls.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
BuildCommandViewSet,
1818
BuildViewSet,
1919
DomainViewSet,
20-
NotificationViewSet,
2120
ProjectViewSet,
2221
RemoteOrganizationViewSet,
2322
RemoteRepositoryViewSet,
@@ -31,7 +30,6 @@
3130
router.register(r'command', BuildCommandViewSet, basename='buildcommandresult')
3231
router.register(r'version', VersionViewSet, basename='version')
3332
router.register(r'project', ProjectViewSet, basename='project')
34-
router.register(r'notification', NotificationViewSet, basename='emailhook')
3533
router.register(r'domain', DomainViewSet, basename='domain')
3634
router.register(r'sphinx_domain', SphinxDomainAPIView, basename='sphinxdomain')
3735
router.register(

readthedocs/api/v2/views/model_views.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -292,15 +292,6 @@ class BuildCommandViewSet(UserSelectViewSet):
292292
model = BuildCommandResult
293293

294294

295-
class NotificationViewSet(viewsets.ReadOnlyModelViewSet):
296-
permission_classes = (permissions.IsAuthenticated, RelatedProjectIsOwner)
297-
renderer_classes = (JSONRenderer,)
298-
model = EmailHook
299-
300-
def get_queryset(self):
301-
return self.model.objects.api(self.request.user)
302-
303-
304295
class DomainViewSet(UserSelectViewSet):
305296
permission_classes = [APIRestrictedPermission]
306297
renderer_classes = (JSONRenderer,)

readthedocs/api/v3/serializers.py

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,6 @@ class BuildSerializer(FlexFieldsModelSerializer):
115115
state = BuildStateSerializer(source='*')
116116
_links = BuildLinksSerializer(source='*')
117117

118-
expandable_fields = dict(
119-
config=(
120-
BuildConfigSerializer,
121-
dict(
122-
source='config',
123-
),
124-
),
125-
)
126-
127118
class Meta:
128119
model = Build
129120
fields = [
@@ -140,6 +131,10 @@ class Meta:
140131
'_links',
141132
]
142133

134+
expandable_fields = {
135+
'config': (BuildConfigSerializer, {'source': 'config'})
136+
}
137+
143138
def get_finished(self, obj):
144139
if obj.date and obj.length:
145140
return obj.date + datetime.timedelta(seconds=obj.length)
@@ -215,15 +210,6 @@ class VersionSerializer(FlexFieldsModelSerializer):
215210
urls = VersionURLsSerializer(source='*')
216211
_links = VersionLinksSerializer(source='*')
217212

218-
expandable_fields = dict(
219-
last_build=(
220-
BuildSerializer,
221-
dict(
222-
source='last_build',
223-
),
224-
),
225-
)
226-
227213
class Meta:
228214
model = Version
229215
fields = [
@@ -241,6 +227,12 @@ class Meta:
241227
'_links',
242228
]
243229

230+
expandable_fields = {
231+
'last_build': (
232+
BuildSerializer, {'source': 'last_build'}
233+
)
234+
}
235+
244236
def get_downloads(self, obj):
245237
downloads = obj.get_downloads()
246238
data = {}
@@ -448,18 +440,6 @@ class ProjectSerializer(FlexFieldsModelSerializer):
448440
created = serializers.DateTimeField(source='pub_date')
449441
modified = serializers.DateTimeField(source='modified_date')
450442

451-
expandable_fields = dict(
452-
active_versions=(
453-
VersionSerializer,
454-
dict(
455-
# NOTE: this has to be a Model method, can't be a
456-
# ``SerializerMethodField`` as far as I know
457-
source='active_versions',
458-
many=True,
459-
),
460-
),
461-
)
462-
463443
class Meta:
464444
model = Project
465445
fields = [
@@ -489,6 +469,18 @@ class Meta:
489469
'_links',
490470
]
491471

472+
expandable_fields = {
473+
'active_versions': (
474+
VersionSerializer,
475+
{
476+
# NOTE: this has to be a Model method, can't be a
477+
# ``SerializerMethodField`` as far as I know
478+
'source': 'active_versions',
479+
'many': True,
480+
}
481+
)
482+
}
483+
492484
def get_homepage(self, obj):
493485
# Overridden only to return ``None`` when the project_url is ``''``
494486
return obj.project_url or None

conftest.py renamed to readthedocs/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
# -*- coding: utf-8 -*-
21
import pytest
3-
from django.conf import settings
42
from rest_framework.test import APIClient
53

4+
65
try:
76
# TODO: this file is read/executed even when called from ``readthedocsinc``,
87
# so it's overriding the options that we are defining in the ``conftest.py``

readthedocs/projects/urls/private.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@
2929
ProjectDelete,
3030
ProjectNotications,
3131
ProjectNoticationsDelete,
32+
ProjectRedirects,
33+
ProjectRedirectsDelete,
3234
ProjectUpdate,
35+
ProjectUsersCreateList,
36+
ProjectUsersDelete,
3337
)
3438

35-
3639
urlpatterns = [
3740
url(r'^$', ProjectDashboard.as_view(), name='projects_dashboard'),
3841
url(
@@ -76,12 +79,14 @@
7679
name='projects_delete',
7780
),
7881
url(
79-
r'^(?P<project_slug>[-\w]+)/users/$', private.project_users,
82+
r'^(?P<project_slug>[-\w]+)/users/$',
83+
ProjectUsersCreateList.as_view(),
8084
name='projects_users',
8185
),
8286
url(
8387
r'^(?P<project_slug>[-\w]+)/users/delete/$',
84-
private.project_users_delete, name='projects_users_delete',
88+
ProjectUsersDelete.as_view(),
89+
name='projects_users_delete',
8590
),
8691
url(
8792
r'^(?P<project_slug>[-\w]+)/notifications/$',
@@ -103,12 +108,14 @@
103108
name='projects_translations_delete',
104109
),
105110
url(
106-
r'^(?P<project_slug>[-\w]+)/redirects/$', private.project_redirects,
111+
r'^(?P<project_slug>[-\w]+)/redirects/$',
112+
ProjectRedirects.as_view(),
107113
name='projects_redirects',
108114
),
109115
url(
110116
r'^(?P<project_slug>[-\w]+)/redirects/delete/$',
111-
private.project_redirects_delete, name='projects_redirects_delete',
117+
ProjectRedirectsDelete.as_view(),
118+
name='projects_redirects_delete',
112119
),
113120
url(
114121
r'^(?P<project_slug>[-\w]+)/advertising/$',

readthedocs/projects/views/private.py

Lines changed: 78 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from django.conf import settings
88
from django.contrib import messages
99
from django.contrib.auth.decorators import login_required
10-
from django.contrib.auth.models import User
1110
from django.db.models import Count
1211
from django.http import (
1312
Http404,
@@ -24,7 +23,14 @@
2423
from django.utils.translation import ugettext_lazy as _
2524
from django.views.generic import ListView, TemplateView, View
2625
from formtools.wizard.views import SessionWizardView
27-
from vanilla import CreateView, DeleteView, DetailView, GenericView, UpdateView
26+
from vanilla import (
27+
CreateView,
28+
DeleteView,
29+
DetailView,
30+
FormView,
31+
GenericView,
32+
UpdateView,
33+
)
2834

2935
from readthedocs.builds.forms import VersionForm
3036
from readthedocs.builds.models import Version
@@ -445,47 +451,49 @@ class ProjectRelationshipDelete(ProjectRelationshipMixin, DeleteView):
445451
http_method_names = ['post']
446452

447453

448-
@login_required
449-
def project_users(request, project_slug):
450-
"""Project users view and form view."""
451-
project = get_object_or_404(
452-
Project.objects.for_admin_user(request.user),
453-
slug=project_slug,
454-
)
454+
class ProjectUsersMixin(ProjectAdminMixin, PrivateViewMixin):
455455

456-
form = UserForm(data=request.POST or None, project=project)
456+
form_class = UserForm
457457

458-
if request.method == 'POST' and form.is_valid():
458+
def get_queryset(self):
459+
project = self.get_project()
460+
return project.users.all()
461+
462+
def get_success_url(self):
463+
return reverse('projects_users', args=[self.get_project().slug])
464+
465+
466+
class ProjectUsersCreateList(ProjectUsersMixin, FormView):
467+
468+
template_name = 'projects/project_users.html'
469+
470+
def form_valid(self, form):
459471
form.save()
460-
project_dashboard = reverse('projects_users', args=[project.slug])
461-
return HttpResponseRedirect(project_dashboard)
472+
return HttpResponseRedirect(self.get_success_url())
473+
474+
def get_context_data(self, **kwargs):
475+
context = super().get_context_data(**kwargs)
476+
context['users'] = self.get_queryset()
477+
return context
462478

463-
users = project.users.all()
464479

465-
return render(
466-
request,
467-
'projects/project_users.html',
468-
{'form': form, 'project': project, 'users': users},
469-
)
480+
class ProjectUsersDelete(ProjectUsersMixin, GenericView):
470481

482+
http_method_names = ['post']
471483

472-
@login_required
473-
def project_users_delete(request, project_slug):
474-
if request.method != 'POST':
475-
return HttpResponseNotAllowed('Only POST is allowed')
476-
project = get_object_or_404(
477-
Project.objects.for_admin_user(request.user),
478-
slug=project_slug,
479-
)
480-
user = get_object_or_404(
481-
User.objects.all(),
482-
username=request.POST.get('username'),
483-
)
484-
if user == request.user:
485-
raise Http404
486-
project.users.remove(user)
487-
project_dashboard = reverse('projects_users', args=[project.slug])
488-
return HttpResponseRedirect(project_dashboard)
484+
def post(self, request, *args, **kwargs):
485+
username = self.request.POST.get('username')
486+
user = get_object_or_404(
487+
self.get_queryset(),
488+
username=username,
489+
)
490+
if user == request.user:
491+
raise Http404
492+
493+
project = self.get_project()
494+
project.users.remove(user)
495+
496+
return HttpResponseRedirect(self.get_success_url())
489497

490498

491499
class ProjecNotificationsMixin(ProjectAdminMixin, PrivateViewMixin):
@@ -617,50 +625,48 @@ def project_translations_delete(request, project_slug, child_slug):
617625
return HttpResponseRedirect(project_dashboard)
618626

619627

620-
@login_required
621-
def project_redirects(request, project_slug):
628+
class ProjectRedirectsMixin(ProjectAdminMixin, PrivateViewMixin):
629+
622630
"""Project redirects view and form view."""
623-
project = get_object_or_404(
624-
Project.objects.for_admin_user(request.user),
625-
slug=project_slug,
626-
)
627631

628-
form = RedirectForm(data=request.POST or None, project=project)
632+
def get_success_url(self):
633+
return reverse(
634+
'projects_redirects',
635+
args=[self.get_project().slug],
636+
)
629637

630-
if request.method == 'POST' and form.is_valid():
638+
639+
class ProjectRedirects(ProjectRedirectsMixin, FormView):
640+
641+
form_class = RedirectForm
642+
template_name = 'projects/project_redirects.html'
643+
644+
def form_valid(self, form):
631645
form.save()
632-
project_dashboard = reverse('projects_redirects', args=[project.slug])
633-
return HttpResponseRedirect(project_dashboard)
646+
return HttpResponseRedirect(self.get_success_url())
634647

635-
redirects = project.redirects.all()
648+
def get_context_data(self, **kwargs):
649+
context = super().get_context_data(**kwargs)
650+
project = self.get_project()
651+
context['redirects'] = project.redirects.all()
652+
return context
636653

637-
return render(
638-
request,
639-
'projects/project_redirects.html',
640-
{'form': form, 'project': project, 'redirects': redirects},
641-
)
642654

655+
class ProjectRedirectsDelete(ProjectRedirectsMixin, GenericView):
643656

644-
@login_required
645-
def project_redirects_delete(request, project_slug):
646-
"""Project redirect delete view."""
647-
if request.method != 'POST':
648-
return HttpResponseNotAllowed('Only POST is allowed')
649-
project = get_object_or_404(
650-
Project.objects.for_admin_user(request.user),
651-
slug=project_slug,
652-
)
653-
redirect = get_object_or_404(
654-
project.redirects,
655-
pk=request.POST.get('id_pk'),
656-
)
657-
if redirect.project == project:
658-
redirect.delete()
659-
else:
660-
raise Http404
661-
return HttpResponseRedirect(
662-
reverse('projects_redirects', args=[project.slug]),
663-
)
657+
http_method_names = ['post']
658+
659+
def post(self, request, *args, **kwargs):
660+
project = self.get_project()
661+
redirect = get_object_or_404(
662+
project.redirects,
663+
pk=request.POST.get('id_pk'),
664+
)
665+
if redirect.project == project:
666+
redirect.delete()
667+
else:
668+
raise Http404
669+
return HttpResponseRedirect(self.get_success_url())
664670

665671

666672
@login_required

readthedocs/rtd_tests/tests/test_search_json_parsing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
# -*- coding: utf-8 -*-
21
import os
32

43
from django.test import TestCase
@@ -13,6 +12,7 @@
1312
class TestHacks(TestCase):
1413

1514
@override_settings(MEDIA_ROOT=base_dir)
15+
@override_settings(PRODUCTION_MEDIA_ARTIFACTS=base_dir)
1616
def test_h2_parsing(self):
1717
data = process_file('files/api.fjson')
1818

0 commit comments

Comments
 (0)