Merge lp:~ricardokirkner/locolander/merge-request-views into lp:locolander

Proposed by Ricardo Kirkner
Status: Merged
Approved by: Natalia Bidart
Approved revision: 27
Merged at revision: 24
Proposed branch: lp:~ricardokirkner/locolander/merge-request-views
Merge into: lp:locolander
Prerequisite: lp:~ricardokirkner/locolander/celery-tasks
Diff against target: 209 lines (+124/-12)
4 files modified
locolander/locolanderweb/templates/locolanderweb/project/detail.html (+24/-9)
locolander/locolanderweb/tests/test_views.py (+77/-0)
locolander/locolanderweb/urls.py (+2/-0)
locolander/locolanderweb/views.py (+21/-3)
To merge this branch: bzr merge lp:~ricardokirkner/locolander/merge-request-views
Reviewer Review Type Date Requested Status
Natalia Bidart Approve
Review via email: mp+174625@code.launchpad.net

Commit message

added view for triggering a merge request run

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Branch looks great. There is only one blocker, and is in the merge_request_run view: the get_object_or_404 should ensure that the user currently logged is the owner of the project that holds the given merge request (otherwise anyone logged in could visit this url by knowing the MR id).

review: Needs Fixing
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Looks good!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'locolander/locolanderweb/templates/locolanderweb/project/detail.html'
--- locolander/locolanderweb/templates/locolanderweb/project/detail.html 2013-07-06 23:19:29 +0000
+++ locolander/locolanderweb/templates/locolanderweb/project/detail.html 2013-07-20 21:25:25 +0000
@@ -4,6 +4,12 @@
4<div class="project-detail">4<div class="project-detail">
5{% if project %}5{% if project %}
6 <h1>Project {{ project.name }}</h1>6 <h1>Project {{ project.name }}</h1>
7 <form action="." method="POST">
8 {% csrf_token %}
9 <div class="actions">
10 <input type="submit" value="Scan this project" />
11 </div>
12 </form>
713
8 {% if project.mergerequest_set.count %}14 {% if project.mergerequest_set.count %}
9 {% for mr in project.mergerequest_set.all %}15 {% for mr in project.mergerequest_set.all %}
@@ -12,14 +18,29 @@
12 <ul>18 <ul>
13 <li>Source: {{ mr.source }}</li>19 <li>Source: {{ mr.source }}</li>
14 <li>Target: {{ mr.target }}</li>20 <li>Target: {{ mr.target }}</li>
21 <li>Author: {{ mr.author }}</li>
15 <li>Reviewers: {{ mr.reviewers }}</li>22 <li>Reviewers: {{ mr.reviewers }}</li>
16 <li>Created: {{ mr.date_created }}</li>23 <li>Created: {{ mr.date_created }}</li>
17 <li>Status: {{ mr.status }}</li>24 <li>Status: {{ mr.status }}</li>
18 </ul>25 </ul>
26 <form action="{% url 'request-run' mr.id %}" method="POST">
27 {% csrf_token %}
28 <div class="actions">
29 <input type="submit" value="Land now!" />
30 </div>
31 </form>
32
19 {% if mr.runlog_set.count %}33 {% if mr.runlog_set.count %}
20 <pre style="background: #eeeeee;">34 {% for entry in mr.runlog_set.all reversed %}
21 {{ mr.runlog_set.latest.timestamp }}35 <div class="runlog-entry">
22 </pre>36 <span>
37 {{ entry.timestamp }}
38 </span>
39 <pre style="background: #eeeeee;">
40 {{ entry.raw|wordwrap:120 }}
41 </pre>
42 </div>
43 {% endfor %}
23 {% endif %}44 {% endif %}
24 </div>45 </div>
25 {% endfor %}46 {% endfor %}
@@ -28,12 +49,6 @@
28 processed merge requests.</h2>49 processed merge requests.</h2>
29 {% endif %}50 {% endif %}
3051
31 <form action="." method="POST">
32 {% csrf_token %}
33 <div class="actions">
34 <input type="submit" value="Scan this project" />
35 </div>
36 </form>
37</div>52</div>
38{% endif %}53{% endif %}
39{% endblock content %}54{% endblock content %}
4055
=== modified file 'locolander/locolanderweb/tests/test_views.py'
--- locolander/locolanderweb/tests/test_views.py 2013-07-06 22:26:19 +0000
+++ locolander/locolanderweb/tests/test_views.py 2013-07-20 21:25:25 +0000
@@ -1,4 +1,6 @@
1from django.core.urlresolvers import reverse1from django.core.urlresolvers import reverse
2from django.utils import html
3from mock import ANY, patch
2from pyquery import PyQuery4from pyquery import PyQuery
35
4from locolanderweb.forms import ProjectForm6from locolanderweb.forms import ProjectForm
@@ -77,3 +79,78 @@
77 response = self.client.get(self.url)79 response = self.client.get(self.url)
7880
79 self.assertContains(response, 'Project %s' % self.project.name)81 self.assertContains(response, 'Project %s' % self.project.name)
82
83 def test_detail_does_not_include_runlog(self):
84 response = self.client.get(self.url)
85
86 self.assertNotContains(response, '<div class="runlog-entry">')
87
88 def test_detail_includes_runlog(self):
89 mr = self.factory.make_merge_request(project=self.project)
90 self.factory.make_run_log(merge_request=mr)
91 self.factory.make_run_log(merge_request=mr)
92 response = self.client.get(self.url)
93
94 self.assertContains(response, '<div class="runlog-entry">', count=2)
95
96 def test_detail_includes_land_now_button(self):
97 merge_request = self.factory.make_merge_request(project=self.project)
98 response = self.client.get(self.url)
99
100 url = reverse('request-run', args=[merge_request.id])
101 self.assertContains(response, '<form action="%s" method="POST">' % url)
102
103 def test_details_includes_author(self):
104 mr = self.factory.make_merge_request(project=self.project)
105
106 response = self.client.get(self.url)
107
108 author_html = "Author: %s" % html.escape(mr.author)
109 self.assertContains(response, author_html)
110
111
112class MergeRequestRunTestCase(BaseTestCase):
113
114 def setUp(self):
115 super(MergeRequestRunTestCase, self).setUp()
116 self.user = self.factory.make_user(password=self.password)
117 assert self.client.login(username=self.user.username,
118 password=self.password)
119 self.project = self.factory.make_project(user=self.user)
120 self.merge_request = self.factory.make_merge_request(
121 project=self.project)
122 self.url = reverse('request-run',
123 kwargs=dict(merge_request_id=self.merge_request.id))
124
125 def test_get(self):
126 response = self.client.get(self.url)
127 self.assertTemplateUsed(response, 'locolanderweb/project/detail.html')
128 self.assertEqual(response.context['project'],
129 self.project)
130
131 def test_get_not_existing(self):
132 url = reverse('request-run', kwargs=dict(merge_request_id=99))
133 response = self.client.get(url)
134 self.assertEqual(response.status_code, 404)
135
136 def test_get_non_owner(self):
137 # make new user
138 user = self.factory.make_user(password=self.password)
139 # login using non-owner user
140 self.client.login(username=user.username, password=self.password)
141
142 response = self.client.get(self.url)
143 self.assertEqual(response.status_code, 404)
144
145 @patch('locolanderweb.views.messages.info')
146 @patch('locolanderweb.tasks.run_request.delay')
147 def test_post(self, mock_delay, mock_info):
148 response = self.client.post(self.url)
149 mock_delay.assert_called_once_with(self.merge_request)
150 msg = 'Merge of {mr.source} into {mr.target} requested.'
151 mock_info.assert_called_once_with(ANY,
152 msg.format(mr=self.merge_request))
153 self.assertRedirects(
154 response,
155 reverse('project-detail',
156 kwargs=dict(project_id=self.project.id)))
80157
=== modified file 'locolander/locolanderweb/urls.py'
--- locolander/locolanderweb/urls.py 2013-07-06 23:19:29 +0000
+++ locolander/locolanderweb/urls.py 2013-07-20 21:25:25 +0000
@@ -13,4 +13,6 @@
13 url(r'^projects/add/$', 'project_add', name='project-add'),13 url(r'^projects/add/$', 'project_add', name='project-add'),
14 url(r'^projects/(?P<project_id>\d+)/$', 'project_detail',14 url(r'^projects/(?P<project_id>\d+)/$', 'project_detail',
15 name='project-detail'),15 name='project-detail'),
16 url(r'requests/(?P<merge_request_id>\d+)/run/$', 'merge_request_run',
17 name='request-run'),
16)18)
1719
=== modified file 'locolander/locolanderweb/views.py'
--- locolander/locolanderweb/views.py 2013-07-06 23:19:29 +0000
+++ locolander/locolanderweb/views.py 2013-07-20 21:25:25 +0000
@@ -5,8 +5,9 @@
5from django.shortcuts import get_object_or_4045from django.shortcuts import get_object_or_404
6from django.template.response import TemplateResponse6from django.template.response import TemplateResponse
77
8from locolanderweb.models import Project8from locolanderweb import tasks
9from locolanderweb.forms import ProjectForm9from locolanderweb.forms import ProjectForm
10from locolanderweb.models import MergeRequest, Project
1011
1112
12@login_required13@login_required
@@ -48,11 +49,28 @@
4849
49 context = dict(project=project)50 context = dict(project=project)
50 if request.method == 'POST':51 if request.method == 'POST':
51 # scan, this is a fake now52 tasks.scan_project.delay(project)
52 project.approved_requests()
53 messages.info(request, 'Scan for %s requested.' % project.name)53 messages.info(request, 'Scan for %s requested.' % project.name)
54 return HttpResponseRedirect(reverse('project-detail',54 return HttpResponseRedirect(reverse('project-detail',
55 kwargs=dict(project_id=project.id)))55 kwargs=dict(project_id=project.id)))
5656
57 return TemplateResponse(57 return TemplateResponse(
58 request, 'locolanderweb/project/detail.html', context=context)58 request, 'locolanderweb/project/detail.html', context=context)
59
60
61@login_required
62def merge_request_run(request, merge_request_id):
63 merge_request = get_object_or_404(
64 MergeRequest, id=merge_request_id, project__owner=request.user)
65 project = merge_request.project
66
67 context = dict(project=project)
68 if request.method == 'POST':
69 tasks.run_request.delay(merge_request)
70 msg = 'Merge of {mr.source} into {mr.target} requested.'
71 messages.info(request, msg.format(mr=merge_request))
72 return HttpResponseRedirect(
73 reverse('project-detail', kwargs=dict(project_id=project.id)))
74
75 return TemplateResponse(
76 request, 'locolanderweb/project/detail.html', context=context)

Subscribers

People subscribed via source and target branches

to all changes: