Skip to content

Commit da5fe54

Browse files
committed
Merge branch 'master' into refactor-views-search-analytics
2 parents bfb9153 + 307aad4 commit da5fe54

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
@@ -27,11 +27,14 @@
2727
ProjectAdvertisingUpdate,
2828
ProjectDashboard,
2929
ProjectDelete,
30+
ProjectRedirects,
31+
ProjectRedirectsDelete,
3032
ProjectUpdate,
33+
ProjectUsersCreateList,
34+
ProjectUsersDelete,
3135
SearchAnalytics,
3236
)
3337

34-
3538
urlpatterns = [
3639
url(r'^$', ProjectDashboard.as_view(), name='projects_dashboard'),
3740
url(
@@ -75,12 +78,14 @@
7578
name='projects_delete',
7679
),
7780
url(
78-
r'^(?P<project_slug>[-\w]+)/users/$', private.project_users,
81+
r'^(?P<project_slug>[-\w]+)/users/$',
82+
ProjectUsersCreateList.as_view(),
7983
name='projects_users',
8084
),
8185
url(
8286
r'^(?P<project_slug>[-\w]+)/users/delete/$',
83-
private.project_users_delete, name='projects_users_delete',
87+
ProjectUsersDelete.as_view(),
88+
name='projects_users_delete',
8489
),
8590
url(
8691
r'^(?P<project_slug>[-\w]+)/notifications/$',
@@ -100,12 +105,14 @@
100105
name='projects_translations_delete',
101106
),
102107
url(
103-
r'^(?P<project_slug>[-\w]+)/redirects/$', private.project_redirects,
108+
r'^(?P<project_slug>[-\w]+)/redirects/$',
109+
ProjectRedirects.as_view(),
104110
name='projects_redirects',
105111
),
106112
url(
107113
r'^(?P<project_slug>[-\w]+)/redirects/delete/$',
108-
private.project_redirects_delete, name='projects_redirects_delete',
114+
ProjectRedirectsDelete.as_view(),
115+
name='projects_redirects_delete',
109116
),
110117
url(
111118
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())
462473

463-
users = project.users.all()
474+
def get_context_data(self, **kwargs):
475+
context = super().get_context_data(**kwargs)
476+
context['users'] = self.get_queryset()
477+
return context
464478

465-
return render(
466-
request,
467-
'projects/project_users.html',
468-
{'form': form, 'project': project, 'users': users},
469-
)
470479

480+
class ProjectUsersDelete(ProjectUsersMixin, GenericView):
471481

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)
482+
http_method_names = ['post']
483+
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
@login_required
@@ -598,50 +606,48 @@ def project_translations_delete(request, project_slug, child_slug):
598606
return HttpResponseRedirect(project_dashboard)
599607

600608

601-
@login_required
602-
def project_redirects(request, project_slug):
609+
class ProjectRedirectsMixin(ProjectAdminMixin, PrivateViewMixin):
610+
603611
"""Project redirects view and form view."""
604-
project = get_object_or_404(
605-
Project.objects.for_admin_user(request.user),
606-
slug=project_slug,
607-
)
608612

609-
form = RedirectForm(data=request.POST or None, project=project)
613+
def get_success_url(self):
614+
return reverse(
615+
'projects_redirects',
616+
args=[self.get_project().slug],
617+
)
610618

611-
if request.method == 'POST' and form.is_valid():
619+
620+
class ProjectRedirects(ProjectRedirectsMixin, FormView):
621+
622+
form_class = RedirectForm
623+
template_name = 'projects/project_redirects.html'
624+
625+
def form_valid(self, form):
612626
form.save()
613-
project_dashboard = reverse('projects_redirects', args=[project.slug])
614-
return HttpResponseRedirect(project_dashboard)
627+
return HttpResponseRedirect(self.get_success_url())
615628

616-
redirects = project.redirects.all()
629+
def get_context_data(self, **kwargs):
630+
context = super().get_context_data(**kwargs)
631+
project = self.get_project()
632+
context['redirects'] = project.redirects.all()
633+
return context
617634

618-
return render(
619-
request,
620-
'projects/project_redirects.html',
621-
{'form': form, 'project': project, 'redirects': redirects},
622-
)
623635

636+
class ProjectRedirectsDelete(ProjectRedirectsMixin, GenericView):
624637

625-
@login_required
626-
def project_redirects_delete(request, project_slug):
627-
"""Project redirect delete view."""
628-
if request.method != 'POST':
629-
return HttpResponseNotAllowed('Only POST is allowed')
630-
project = get_object_or_404(
631-
Project.objects.for_admin_user(request.user),
632-
slug=project_slug,
633-
)
634-
redirect = get_object_or_404(
635-
project.redirects,
636-
pk=request.POST.get('id_pk'),
637-
)
638-
if redirect.project == project:
639-
redirect.delete()
640-
else:
641-
raise Http404
642-
return HttpResponseRedirect(
643-
reverse('projects_redirects', args=[project.slug]),
644-
)
638+
http_method_names = ['post']
639+
640+
def post(self, request, *args, **kwargs):
641+
project = self.get_project()
642+
redirect = get_object_or_404(
643+
project.redirects,
644+
pk=request.POST.get('id_pk'),
645+
)
646+
if redirect.project == project:
647+
redirect.delete()
648+
else:
649+
raise Http404
650+
return HttpResponseRedirect(self.get_success_url())
645651

646652

647653
@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)