Merge lp:~daker/loco-team-portal/fix.876659 into lp:loco-team-portal

Proposed by Adnane Belmadiaf
Status: Merged
Approved by: Adnane Belmadiaf
Approved revision: 568
Merged at revision: 568
Proposed branch: lp:~daker/loco-team-portal/fix.876659
Merge into: lp:loco-team-portal
Diff against target: 474 lines (+74/-107)
12 files modified
loco_directory/common/utils.py (+0/-12)
loco_directory/events/managers.py (+36/-0)
loco_directory/events/models.py (+10/-52)
loco_directory/events/urls.py (+4/-3)
loco_directory/events/views.py (+10/-16)
loco_directory/meetings/views.py (+2/-7)
loco_directory/teams/tests.py (+2/-2)
loco_directory/teams/views.py (+2/-6)
loco_directory/templates/events/team_event_detail.html (+1/-1)
loco_directory/templates/events/team_event_detail.inc.html (+5/-5)
loco_directory/templates/teams/team_detail.html (+1/-1)
loco_directory/userprofiles/models.py (+1/-2)
To merge this branch: bzr merge lp:~daker/loco-team-portal/fix.876659
Reviewer Review Type Date Requested Status
LoCo Team Portal Developers Pending
Review via email: mp+136548@code.launchpad.net

Commit message

* More SEO friendly urls
* Fixed the tests
* Moved the manages to a sperate file

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'loco_directory/common/utils.py'
2--- loco_directory/common/utils.py 2012-11-09 23:35:39 +0000
3+++ loco_directory/common/utils.py 2012-11-28 00:51:20 +0000
4@@ -49,18 +49,6 @@
5 return reduce(lambda a, b: a.extend(b) or a, some_list)
6
7
8-# pylint: disable-msg=C0103
9-def redirect(to, *args, **kwargs):
10- from distutils.version import LooseVersion as V
11- import django
12- if V(django.get_version()) > V('1.1'):
13- from django.shortcuts import redirect as red
14- else:
15- from shortcuts import redirect as red
16-
17- return red(to, *args, **kwargs)
18-
19-
20 def get_locodirectory_version(version_file, debug):
21 """
22 return the bzr revision number and version of the LoCo Team Portal
23
24=== added file 'loco_directory/events/managers.py'
25--- loco_directory/events/managers.py 1970-01-01 00:00:00 +0000
26+++ loco_directory/events/managers.py 2012-11-28 00:51:20 +0000
27@@ -0,0 +1,36 @@
28+# -*- coding: utf-8 -*-
29+from django.db import models
30+import datetime
31+
32+
33+class GlobalEventManager(models.Manager):
34+ """ manager for a global event """
35+ def next_events(self):
36+ """ a list with all upcoming global events """
37+ return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')
38+
39+ def history_events(self):
40+ """ all global events in history """
41+ return self.filter(date_end__lt=datetime.datetime.now()).order_by('date_end')
42+
43+
44+class TeamEventManager(models.Manager):
45+ """ manager for a team event """
46+ def next_3_events(self):
47+ """ a list with the next 3 events """
48+ return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')[:3]
49+
50+ def next_5_events(self):
51+ """ a list with the next 5 events """
52+ return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')[:5]
53+
54+ def next_events(self):
55+ """ a list with all upcoming team events """
56+ return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')
57+
58+ def history_events(self):
59+ """ all team events in history """
60+ return self.filter(date_end__lt=datetime.datetime.now()).order_by('-date_end')
61+
62+ def has_location(self):
63+ return self.filter(venue__longitude__isnull=False, venue__latitude__isnull=False)
64
65=== modified file 'loco_directory/events/models.py'
66--- loco_directory/events/models.py 2012-06-30 23:44:14 +0000
67+++ loco_directory/events/models.py 2012-11-28 00:51:20 +0000
68@@ -1,11 +1,16 @@
69+# -*- coding: utf-8 -*-
70 from django.db import models
71+from django.template.defaultfilters import slugify
72+from django.utils.translation import ugettext_lazy as _
73+
74 from userprofiles.models import UserProfile
75 from teams.models import *
76 from venues.models import Venue
77-from django.utils.translation import ugettext_lazy as _
78 from common.shortcuts import queryset_sum, queryset_count
79 from common.mixins import LocalTimeMixin
80
81+from managers import GlobalEventManager, TeamEventManager
82+
83 ATTENDEE_PROMISE_CHOICES = (
84 ('sure', _('attending')),
85 ('maybe', _('might be attending')),
86@@ -46,25 +51,6 @@
87 return self.date_end > datetime.datetime.today()
88
89
90-class GlobalEventManager(models.Manager):
91- """ manager for a global event """
92- def next_events(self):
93- """ a list with all upcoming global events """
94- return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')
95-
96- def history_events(self):
97- """ all global events in history """
98- return self.filter(date_end__lt=datetime.datetime.now()).order_by('date_end')
99-
100- def random_5(self):
101- """ return 5 random Team Events for this global event """
102- return TeamEvent.objects.filter(global_event__id__exact=self.id).order_by('?')[:5]
103-
104- def last_5(self):
105- """ return the last 5 active Team events for this global event"""
106- return TeamEvent.objects.filter(global_event__id__exact=self.id).order_by('?')[5:]
107-
108-
109 class GlobalEvent(BaseEvent):
110 """
111 a global event. other events can attend to a global event
112@@ -94,34 +80,6 @@
113 'countries': countries})
114
115
116-class TeamEventManager(models.Manager):
117- """ manager for a team event """
118- def next_3_events(self):
119- """ a list with the next 3 events """
120- return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')[:3]
121-
122- def next_5_events(self):
123- """ a list with the next 5 events """
124- return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')[:5]
125-
126- def next_events(self):
127- """ a list with all upcoming team events """
128- return self.filter(date_end__gt=datetime.datetime.now()).order_by('date_end')
129-
130- def history_events(self):
131- """ all team events in history """
132- return self.filter(date_end__lt=datetime.datetime.now()).order_by('-date_end')
133-
134- def attending(self):
135- return Attendee.objects.filter(event__id__exact=self.id)
136-
137- def random_5_attendees(self):
138- return Attendee.objects.filter(event__id__exact=self.id).order_by('?')[5:]
139-
140- def has_location(self):
141- return self.filter(venue__longitude__isnull=False, venue__latitude__isnull=False)
142-
143-
144 class TeamEvent(BaseEvent, LocalTimeMixin):
145 """
146 a event of one or more teams
147@@ -141,7 +99,7 @@
148
149 @models.permalink
150 def get_absolute_url(self):
151- return ('team-event-detail', [getattr(self.first_team(), 'lp_name', 'no-team'), str(self.id)])
152+ return ('team-event-detail', [getattr(self.first_team(), 'lp_name', 'no-team'), str(self.id), slugify(self.name)])
153
154 def get_tz(self):
155 timezone = 'UTC'
156@@ -183,7 +141,7 @@
157 try:
158 return self.teams.all()[0]
159 except:
160- print 'Event %s has no team' % self.id
161+ #print 'Event %s has no team' % self.id
162 return {'lp_name': 'no-team'}
163
164 def is_attending(self, user):
165@@ -195,13 +153,13 @@
166 def total_attending(self):
167 total_attending_guests = queryset_sum('guests', Attendee.objects.filter(team_event__id__exact=self.id).filter(promise="sure"))
168 total_attending_registered = queryset_count('attendee_profile', Attendee.objects.filter(team_event__id__exact=self.id).filter(promise="sure"))
169- return total_attending_guests + total_attending_registered
170+ return total_attending_guests + total_attending_registered
171
172 def total_maybe_attending(self):
173
174 total_maybe_attending_guests = queryset_sum('guests', Attendee.objects.filter(team_event__id__exact=self.id).filter(promise="maybe"))
175 total_maybe_attending_registered = queryset_count('attendee_profile', Attendee.objects.filter(team_event__id__exact=self.id).filter(promise="maybe"))
176- return total_maybe_attending_guests + total_maybe_attending_registered
177+ return total_maybe_attending_guests + total_maybe_attending_registered
178
179
180 class TeamEventComment(models.Model):
181
182=== modified file 'loco_directory/events/urls.py'
183--- loco_directory/events/urls.py 2012-09-08 21:45:10 +0000
184+++ loco_directory/events/urls.py 2012-11-28 00:51:20 +0000
185@@ -20,19 +20,20 @@
186 #team events
187 url(r'^locations/$', 'events.views.team_event_locations', name='team-event-locations'),
188 url(r'^ical/$', 'events.views.teams_event_list_ical', name='teams-event-list-ical'),
189+ url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)-(?P<event_slug>[a-zA-Z0-9\-\.\+?]+)/$', 'events.views.team_event_detail', name='team-event-detail'),
190 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)/detail/ical/$', 'events.views.event_ical', name='event-ical'),
191 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)/register/$', 'events.views.team_event_register', name='team-event-register'),
192- url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)/detail/$', 'events.views.team_event_detail', name='team-event-detail'),
193+ url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)/detail/$', 'events.views.team_event_detail', name='team-event-detail-old'),
194 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)/delete/$', 'events.views.team_event_delete', name='team-event-delete'),
195 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)/update/$', 'events.views.team_event_update', name='team-event-update'),
196 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/(?P<team_event_id>\d+)/copy/$', 'events.views.team_event_copy', name='team-event-copy'),
197 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/add/$', 'events.views.team_event_new', name='team-event-new'),
198 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/rss/$', 'events.views.team_events_rss', name='team-events-rss'),
199 url(r'^(?P<team_slug>[a-zA-Z0-9\-\.\+?]+)/ical/$', 'events.views.team_event_list_ical', name='team-event-list-ical'),
200- url(r'^add/$', 'events.views.team_event_select', name='team-event-select'),
201+ url(r'^add/$', 'events.views.team_event_select', name='team-event-select'),
202 url(r'^comment-update/$', 'events.views.team_event_comment_edit', name='team-event-comment-edit'),
203 url(r'^comment-delete/(?P<pk>\d+)/$', 'events.views.team_event_comment_delete', name='team-event-comment-delete'),
204-
205+
206 # Old url notations
207 url(r'^team/locations/$', 'events.views.team_event_locations'),
208 url(r'^team/ical/$', 'events.views.teams_event_list_ical'),
209
210=== modified file 'loco_directory/events/views.py'
211--- loco_directory/events/views.py 2012-11-26 23:07:59 +0000
212+++ loco_directory/events/views.py 2012-11-28 00:51:20 +0000
213@@ -16,7 +16,7 @@
214 from forms import GlobalEventForm
215 from forms import AttendeeRegistrationForm
216
217-from common.utils import redirect, simple_iterator
218+from common.utils import simple_iterator
219 from common.forms import FilterHistoryList
220 from common import launchpad
221
222@@ -133,7 +133,7 @@
223 #################################################################
224 # Team Events
225 #################################################################
226-def team_event_detail(request, team_slug, team_event_id):
227+def team_event_detail(request, team_slug, team_event_id, event_slug=None):
228 """
229 detailed view for a team event
230 """
231@@ -148,9 +148,7 @@
232 if form.is_valid():
233 team_event_comment = form.save(commit=False)
234 team_event_comment.team_event = team_event_object
235- from userprofiles.models import create_profile
236- profile = create_profile(request.user.username)
237- team_event_comment.commenter_profile = profile
238+ team_event_comment.commenter_profile = request.user.get_profile()
239 team_event_comment.save()
240 request.user.message_set.create(message=_('Your comment has been saved.'))
241 return redirect(team_event_object)
242@@ -192,7 +190,6 @@
243 else:
244 context = {'team_event_object': team_event_object}
245 return render_to_response('events/team_event_delete_confirm.html', context, RequestContext(request))
246-
247 else:
248 request.user.message_set.create(message='%s %s' % (_('You can not remove this team event.'), _('You are not an admin/owner of the Launchpad team or on the LoCo Council.')))
249 return redirect(team_event_object)
250@@ -207,7 +204,7 @@
251 elif len(teams) == 1:
252 from django.core import urlresolvers
253 url = urlresolvers.reverse('team-event-new', args=[teams[0].lp_name])
254- if request.GET.has_key('global_event_id'):
255+ if 'global_event_id' in request.GET:
256 return HttpResponseRedirect('%s?global_event_id=%s' % (url, request.GET.get('global_event_id', None)))
257 else:
258 return HttpResponseRedirect(url)
259@@ -240,7 +237,7 @@
260 return HttpResponseRedirect(team_event.get_absolute_url())
261 else:
262 form = TeamEventForm(initial={'global_event': request.GET.get('global_event_id', None)}, teams=[team_object])
263- if request.GET.has_key('global_event_id'):
264+ if 'global_event_id' in request.GET:
265 global_event = get_object_or_404(GlobalEvent, pk=request.GET.get('global_event_id'))
266 context = {
267 'team_object': team_object,
268@@ -331,11 +328,9 @@
269 """
270 team_event_object = get_object_or_404(TeamEvent, pk=team_event_id)
271 if team_event_object.is_attending(request.user):
272- attendee_object = team_event_object.attendee_set.get(attendee_profile__user=request.user)
273+ attendee_object = team_event_object.attendee_set.get(attendee_profile=request.user.get_profile())
274 else:
275- from userprofiles.models import create_profile
276- profile = create_profile(request.user.username)
277- attendee_object = Attendee(team_event=team_event_object, attendee_profile=profile)
278+ attendee_object = Attendee(team_event=team_event_object, attendee_profile=request.user.get_profile())
279
280 if request.method == 'POST':
281 form = AttendeeRegistrationForm(instance=attendee_object, data=request.POST)
282@@ -358,6 +353,7 @@
283 return render_to_response('events/team_event_register.html',
284 context, RequestContext(request))
285
286+
287 @login_required
288 def team_event_comment_new(request, team_slug, team_event_id):
289 """
290@@ -370,12 +366,10 @@
291 if form.is_valid():
292 team_event_comment = form.save(commit=False)
293 team_event_comment.team_event = team_event_object
294- from userprofiles.models import create_profile
295- profile = create_profile(request.user.username)
296- team_event_comment.commenter_profile = profile
297+ team_event_comment.commenter_profile = request.user.get_profile()
298 team_event_comment.save()
299 request.user.message_set.create(message=_('Comment saved.'))
300- return redirect( team_event_object )
301+ return redirect(team_event_object)
302 else:
303 form = TeamEventCommentForm()
304
305
306=== modified file 'loco_directory/meetings/views.py'
307--- loco_directory/meetings/views.py 2012-11-26 23:07:59 +0000
308+++ loco_directory/meetings/views.py 2012-11-28 00:51:20 +0000
309@@ -1,7 +1,6 @@
310 from django.template import RequestContext
311 from django.http import HttpResponse, HttpResponseRedirect
312-from django.shortcuts import render_to_response
313-from django.shortcuts import get_object_or_404
314+from django.shortcuts import render_to_response, redirect, get_object_or_404
315 from django.contrib.auth.decorators import login_required
316 from django.utils.translation import ugettext as _
317 from django.core.urlresolvers import reverse
318@@ -11,9 +10,6 @@
319
320 from forms import TeamMeetingForm, AgendaItemForm
321
322-from django.db.models import Q
323-
324-from common.utils import redirect, simple_iterator
325 from common import launchpad
326 from common.forms import FilterHistoryList
327
328@@ -164,8 +160,7 @@
329 request.user.message_set.create(message='%s %s' % (_('You can not remove this team meeting.'), _('You are not a member of any LoCo Teams.')))
330 return redirect( 'meeting-list' )
331 elif len(teams) == 1:
332- from django.core import urlresolvers
333- url = urlresolvers.reverse('team-meeting-new', args=[teams[0].lp_name])
334+ url = reverse('team-meeting-new', args=[teams[0].lp_name])
335 return HttpResponseRedirect(url)
336 else:
337 context = {'teams': teams}
338
339=== modified file 'loco_directory/teams/tests.py'
340--- loco_directory/teams/tests.py 2012-11-23 21:40:17 +0000
341+++ loco_directory/teams/tests.py 2012-11-28 00:51:20 +0000
342@@ -26,7 +26,7 @@
343 claimed_id='http://example.com/identity_foo',
344 display_id='http://example.com/identity_foo')
345 self.useropenid_foo.save()
346- self.userprofile_foo = UserProfile.objects.create(user=self.user_foo)
347+ self.userprofile_foo = self.user_foo.get_profile()
348
349 # user bar
350 self.user_bar = User.objects.create(
351@@ -38,7 +38,7 @@
352 claimed_id='http://example.com/identity_bar',
353 display_id='http://example.com/identity_bar')
354 self.useropenid_bar.save()
355- self.userprofile_bar = UserProfile.objects.create(user=self.user_bar)
356+ self.userprofile_bar = self.user_bar.get_profile()
357
358 # setup test country
359 self.country = Country.objects.create(name='Test Country')
360
361=== modified file 'loco_directory/teams/views.py'
362--- loco_directory/teams/views.py 2012-11-26 23:07:59 +0000
363+++ loco_directory/teams/views.py 2012-11-28 00:51:20 +0000
364@@ -1,18 +1,14 @@
365 # -*- coding: utf-8 -*-
366-
367 from django.template import RequestContext
368 from django.utils.translation import ugettext
369 from django.core import serializers
370
371+from django.shortcuts import render_to_response, redirect, get_object_or_404
372 from django.contrib.auth.decorators import login_required
373-from django.db.models import Q
374-
375-from django.shortcuts import get_object_or_404
376-from django.shortcuts import render_to_response
377
378 from django import http
379
380-from common.utils import redirect, simple_iterator
381+from common.utils import simple_iterator
382 from common import launchpad
383
384 from teams.models import Continent, Team, countries_without_continent, countries_without_continent_have_teams, teams_without_country
385
386=== modified file 'loco_directory/templates/events/team_event_detail.html'
387--- loco_directory/templates/events/team_event_detail.html 2012-09-08 21:45:10 +0000
388+++ loco_directory/templates/events/team_event_detail.html 2012-11-28 00:51:20 +0000
389@@ -9,7 +9,7 @@
390 {% if team_event_object.description %}
391 <meta property="og:description" content="{{ team_event_object.description|linebreaks|striptags }}" />
392 {% endif %}
393-<meta property="og:url" content="http://loco.ubuntu.com{% url team-event-detail team_event_object.first_team.lp_name team_event_object.id %}"/>
394+<meta property="og:url" content="http://loco.ubuntu.com{{ team_event_object.get_absolute_url }}"/>
395 <meta property="og:image" content="http://loco.ubuntu.com/media/images/cof_orange_hex1.png"/>
396 <meta property="og:site_name" content="Loco Team Portal"/>
397 <meta property="og:type" content="loco-team-portal:event"/>
398
399=== modified file 'loco_directory/templates/events/team_event_detail.inc.html'
400--- loco_directory/templates/events/team_event_detail.inc.html 2012-09-08 21:45:10 +0000
401+++ loco_directory/templates/events/team_event_detail.inc.html 2012-11-28 00:51:20 +0000
402@@ -6,7 +6,7 @@
403 <h3>{{ team_event_object.name }}</h3>
404 {% if team_event_object.global_event %}
405 <div class="event-partofglobal-event">
406- {% trans "This event is part of" %}
407+ {% trans "This event is part of" %}
408 <a href="{{ team_event_object.global_event.get_absolute_url }}">{{team_event_object.global_event.name }}</a>
409 </div>
410 {% endif %}
411@@ -23,7 +23,7 @@
412 <script type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>
413 <g:plusone size="medium"></g:plusone>
414 <a href="http://twitter.com/share" class="twitter-share-button" data-lang="en">Tweet</a>
415- <div class="fb-like" data-href="http://loco.ubuntu.com{% url team-event-detail team_event_object.first_team.lp_name team_event_object.id %}" data-send="false" data-layout="button_count" data-width="40" data-show-faces="false" data-font="arial"></div>
416+ <div class="fb-like" data-href="http://loco.ubuntu.com{{ team_event_object.get_absolute_url }}" data-send="false" data-layout="button_count" data-width="40" data-show-faces="false" data-font="arial"></div>
417 </div>
418
419 {% if team_event_object.global_event %}
420@@ -50,7 +50,7 @@
421 </div>
422 {% endif %}
423 </div>
424-
425+
426 <div class="map">
427 <img class="map_img" src="http://maps.googleapis.com/maps/api/staticmap?center=Brooklyn+Bridge,New+York,NY&zoom=13&size=640x100&scale=1&maptype=roadmap&markers=color:orange%7Clabel:S%7C40.702147,-74.015794&sensor=false">
428 </div>
429@@ -67,7 +67,7 @@
430 </div>
431
432 <div class="event-venue-adress">
433- {% if team_event_object.venue.address %}{{ team_event_object.venue.address }}{% endif %}{% if team_event_object.venue.spr %}, {{ team_event_object.venue.spr }}{% endif %}
434+ {% if team_event_object.venue.address %}{{ team_event_object.venue.address }}{% endif %}{% if team_event_object.venue.spr %}, {{ team_event_object.venue.spr }}{% endif %}
435 </div>
436 </div>
437 {% endif %}
438@@ -104,7 +104,7 @@
439 {% for team in team_event_object.teams.all %}<a class="pictogram-l" title="{% trans "Get more information about this team" %}" href="{{ team.get_absolute_url }}">{{ team.name }}</a>{% if not forloop.last %},{% endif %}{% endfor %}
440 </div>
441 {% endif %}
442- {% endif %}
443+ {% endif %}
444 </div>
445
446 {% if team_event_object.registration %}
447
448=== modified file 'loco_directory/templates/teams/team_detail.html'
449--- loco_directory/templates/teams/team_detail.html 2012-11-26 23:07:59 +0000
450+++ loco_directory/templates/teams/team_detail.html 2012-11-28 00:51:20 +0000
451@@ -185,7 +185,7 @@
452 </div>
453 {% if team.next_5_events_and_meetings %}
454 {% for team_event in team.next_5_events_and_meetings %}
455- <a href="{% url team-event-detail team_event.first_team.lp_name team_event.id %}" title="{% if team_event.venue %}{% else %}{{team_event.date_begin|date:"M d"}}{% endif %}" class="event-meetings">
456+ <a href="{{ team_event.get_absolute_url }}" title="{% if team_event.venue %}{% else %}{{team_event.date_begin|date:"M d"}}{% endif %}" class="event-meetings">
457 <span class="title">{{ team_event.name }}</span>
458 <small>{{ team_event.local_date_begin|date:"l, d N Y" }} {% trans "at " %}{{ team_event.local_date_begin|date:"H:i T" }}</small>
459 <p>{{ team_event.description }}</p>
460
461=== modified file 'loco_directory/userprofiles/models.py'
462--- loco_directory/userprofiles/models.py 2012-11-18 22:02:56 +0000
463+++ loco_directory/userprofiles/models.py 2012-11-28 00:51:20 +0000
464@@ -102,9 +102,8 @@
465
466 post_save.connect(create_userprofile, sender=auth_models.User, dispatch_uid=__name__)
467
468+
469 def create_profile(username):
470 user, created = auth_models.User.objects.get_or_create(username=username)
471- if created:
472- user.save()
473 set_user_openid(user, force=True)
474 return user.get_profile()

Subscribers

People subscribed via source and target branches