Redo our URL scheme to use {% url %} tags
Updated 8 months, 3 weeks ago
| Christian Hammond | Reviewers | ||
| trunk | reviewboard | ||
| 458, 459 | |||
| None | Review Board SVN | ||
We've been using the old get_absolute_url() function in our templates, which works for the most part but has limitations. Using it on a User object doesn't work so well when installed in a subdirectory, requiring that we prepend SITE_ROOT and then slice the first "/". This gets ugly fast.
We've also been hard-coding other paths ("/account/login/", "/dashboard/", etc.), requiring prepending SITE_ROOT again.
Django's {% url %} tag provides a cleaner solution, allowing us to give certain pages names that we can refer to. Now something like {{SITE_ROOT}}{{submitter.get_absolute_url|slice:"1"}} turns into {% url user submitter %}.
This in theory could also give extensions in the future the ability to override certain pages if they wanted.
The result of this change is that we use get_absolute_url() a lot less for some things and never for users. We could move the rest of the get_absolute_url() calls in the future if we choose, though what we have works for now. We've also eliminated most references to SITE_ROOT in the templates.
There's some misc. fixes and cleanups in here too, such as using djblets's new user_displayname filter.
Made sure every page I tested worked properly in subdirectory installs. All the user links I could find now point to the correct location.
Diff revision 1 (Latest)
- /trunk/reviewboard/urls.py: 14 changes [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ]
- /trunk/reviewboard/accounts/decorators.py: 2 changes [ 1 2 ]
- /trunk/reviewboard/accounts/views.py: 4 changes [ 1 2 3 4 ]
- /trunk/reviewboard/iphone/urls.py: 17 changes [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ]
- /trunk/reviewboard/reviews/datagrids.py: 2 changes [ 1 2 ]
- /trunk/reviewboard/reviews/urls.py: 4 changes [ 1 2 3 4 ]
- /trunk/reviewboard/reviews/views.py: 3 changes [ 1 2 3 ]
- /trunk/reviewboard/reviews/templatetags/reviewtags.py: 2 changes [ 1 2 ]
- /trunk/reviewboard/templates/base.html: 9 changes [ 1 2 3 4 5 6 7 8 9 ]
- /trunk/reviewboard/templates/base_feeds.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/accounts/login.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/diffviewer/view_diff.html: 2 changes [ 1 2 ]
- /trunk/reviewboard/templates/iphone/base.html: 3 changes [ 1 2 3 ]
- /trunk/reviewboard/templates/iphone/dashboard.html: 4 changes [ 1 2 3 4 ]
- /trunk/reviewboard/templates/iphone/diff_files.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/iphone/group_list.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/iphone/login.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/iphone/review_request_detail.html: 5 changes [ 1 2 3 4 5 ]
- /trunk/reviewboard/templates/iphone/review_request_list.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/iphone/submitter_list.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/reviews/dashboard_entry.html: 1 change [ 1 ]
- /trunk/reviewboard/templates/reviews/review_detail.html: 3 changes [ 1 2 3 ]
- /trunk/reviewboard/templates/reviews/review_reply.html: 2 changes [ 1 2 ]
- /trunk/reviewboard/templates/reviews/review_request_box.html: 3 changes [ 1 2 3 ]
- /trunk/reviewboard/templates/reviews/search.html: 3 changes [ 1 2 3 ]
| /trunk/reviewboard/urls.py | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| 1 |
|
1 |
|
| 2 | 2 | ||
| 3 | from django.conf import settings |
3 | from django.conf import settings |
| 4 | from django.conf.urls.defaults import patterns, include, handler404, handler500 |
4 | from django.conf.urls.defaults import patterns, include, url |
| 5 | 5 | ||
| 6 | from reviewboard.admin.checks import check_updates_required |
6 | from reviewboard.admin.checks import check_updates_required |
| 7 | from reviewboard.reviews.feeds import RssReviewsFeed, AtomReviewsFeed, \ |
7 | from reviewboard.reviews.feeds import RssReviewsFeed, AtomReviewsFeed, \ |
| 8 | RssSubmitterReviewsFeed, \ |
8 | RssSubmitterReviewsFeed, \ |
| 9 | AtomSubmitterReviewsFeed, \ |
9 | AtomSubmitterReviewsFeed, \ |
| ... | 50 lines hidden [Expand] | ||
| 60 | 60 | ||
| 61 | 61 | ||
| 62 | # reviewboard.reviews.views |
62 | # reviewboard.reviews.views |
| 63 | urlpatterns += patterns('reviewboard.reviews.views', |
63 | urlpatterns += patterns('reviewboard.reviews.views', |
| 64 | # Review request browsing |
64 | # Review request browsing |
| 65 | (r'^dashboard/$', 'dashboard'), |
65 | url(r'^dashboard/$', 'dashboard', name="dashboard"), |
| 66 | 66 | ||
| 67 | # Users |
67 | # Users |
| 68 | (r'^users/$', 'submitter_list'), |
68 | url(r'^users/$', 'submitter_list', name="all-users"), |
| 69 | (r'^users/(?P<username>[A-Za-z0-9_\-\.]+)/$', 'submitter'), |
69 | url(r'^users/(?P<username>[A-Za-z0-9_\-\.]+)/$', 'submitter', |
| 70 | name="user"), |
||
| 70 | 71 | ||
| 71 | # Groups |
72 | # Groups |
| 72 | (r'^groups/$', 'group_list'), |
73 | url(r'^groups/$', 'group_list', name="all-groups"), |
| 73 | (r'^groups/(?P<name>[A-Za-z0-9_-]+)/$', 'group'), |
74 | url(r'^groups/(?P<name>[A-Za-z0-9_-]+)/$', 'group', name="group"), |
| 74 | ) |
75 | ) |
| 75 | 76 | ||
| 76 | 77 | ||
| 77 | # django.contrib |
78 | # django.contrib |
| 78 | urlpatterns += patterns('django.contrib', |
79 | urlpatterns += patterns('django.contrib', |
| 79 | # Feeds |
80 | # Feeds |
| 80 | (r'^feeds/rss/(?P<url>.*)/$', 'syndication.views.feed', |
81 | url(r'^feeds/rss/(?P<url>.*)/$', 'syndication.views.feed', |
| 81 | {'feed_dict': rss_feeds}), |
82 | {'feed_dict': rss_feeds}, |
| 82 | (r'^feeds/atom/(?P<url>.*)/$', 'syndication.views.feed', |
83 | name="rss-feed"), |
| 83 | {'feed_dict': atom_feeds}), |
84 | url(r'^feeds/atom/(?P<url>.*)/$', 'syndication.views.feed', |
| 84 | (r'^account/logout/$', 'auth.views.logout', |
85 | {'feed_dict': atom_feeds}, |
| 85 | {'next_page': settings.LOGIN_URL}), |
86 | name="atom-feed"), |
| 87 | url(r'^account/logout/$', 'auth.views.logout', |
||
| 88 | {'next_page': settings.LOGIN_URL}, |
||
| 89 | name="logout") |
||
| 86 | ) |
90 | ) |
| 87 | 91 | ||
| 88 | 92 | ||
| 89 | # And the rest ... |
93 | # And the rest ... |
| 90 | urlpatterns += patterns('', |
94 | urlpatterns += patterns('', |
| 91 | (r'^$', 'django.views.generic.simple.redirect_to', |
95 | url(r'^$', 'django.views.generic.simple.redirect_to', |
| 92 | {'url': 'dashboard/'}), |
96 | {'url': 'dashboard/'}, |
| 97 | name="root"), |
||
| 93 | 98 | ||
| 94 | # Authentication and accounts |
99 | # Authentication and accounts |
| 95 | (r'^account/login/$', 'djblets.auth.views.login', |
100 | url(r'^account/login/$', 'djblets.auth.views.login', |
| 96 | {'next_page': settings.SITE_ROOT + 'dashboard/', |
101 | {'next_page': settings.SITE_ROOT + 'dashboard/', |
| 97 | 'extra_context': {'BUILTIN_AUTH': settings.BUILTIN_AUTH}}), |
102 | 'extra_context': {'BUILTIN_AUTH': settings.BUILTIN_AUTH}}, |
| 98 | (r'^account/preferences/$', 'reviewboard.accounts.views.user_preferences',), |
103 | name="login"), |
| 104 | url(r'^account/preferences/$', |
||
| 105 | 'reviewboard.accounts.views.user_preferences', |
||
| 106 | name="user-preferences"), |
||
| 99 | 107 | ||
| 100 | # This must be last. |
108 | # This must be last. |
| 101 | (r'^iphone/', include('reviewboard.iphone.urls')), |
109 | (r'^iphone/', include('reviewboard.iphone.urls')), |
| 102 | ) |
110 | ) |
| 103 | 111 | ||
| 104 | if settings.BUILTIN_AUTH: |
112 | if settings.BUILTIN_AUTH: |
| 105 | urlpatterns += patterns('', |
113 | urlpatterns += patterns('', |
| 106 | (r'^account/register/$', 'djblets.auth.views.register', |
114 | url(r'^account/register/$', 'djblets.auth.views.register', |
| 107 | {'next_page': settings.SITE_ROOT + 'dashboard/'}), |
115 | {'next_page': settings.SITE_ROOT + 'dashboard/'}, |
| 116 | name="register"), |
||
| 108 | ) |
117 | ) |
| 109 | else: |
118 | else: |
| 110 | urlpatterns += patterns('', |
119 | urlpatterns += patterns('', |
| 111 | (r'^account/register/$', |
120 | (r'^account/register/$', |
| 112 | 'django.views.generic.simple.redirect_to', |
121 | 'django.views.generic.simple.redirect_to', |
| 113 | {'url': settings.SITE_ROOT + 'account/login/'})) |
122 | {'url': settings.SITE_ROOT + 'account/login/'})) |
| /trunk/reviewboard/accounts/decorators.py | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| 1 |
|
1 |
|
| 2 | 2 | ||
| 3 | from django.conf import settings |
3 | from django.conf import settings |
| 4 | from django.contrib.auth import REDIRECT_FIELD_NAME |
4 | from django.contrib.auth import REDIRECT_FIELD_NAME |
| 5 | from django.core.urlresolvers import reverse |
||
| 5 | from django.http import HttpResponseRedirect |
6 | from django.http import HttpResponseRedirect |
| 6 | 7 | ||
| 7 | from djblets.auth.util import login_required |
8 | from djblets.auth.util import login_required |
| 8 | from djblets.util.decorators import simple_decorator |
9 | from djblets.util.decorators import simple_decorator |
| 9 | 10 | ||
| ... | 29 lines hidden [Expand] | ||
| 39 | if profile.first_time_setup_done: |
40 | if profile.first_time_setup_done: |
| 40 | return view_func(request, *args, **kwargs) |
41 | return view_func(request, *args, **kwargs) |
| 41 | except Profile.DoesNotExist: |
42 | except Profile.DoesNotExist: |
| 42 | pass |
43 | pass |
| 43 | 44 | ||
| 44 | return HttpResponseRedirect("%saccount/preferences/?%s=%s" % |
45 | return HttpResponseRedirect("%s?%s=%s" % |
| 45 | (settings.SITE_ROOT, |
46 | (reverse("user-preferences"), |
| 46 | REDIRECT_FIELD_NAME, |
47 | REDIRECT_FIELD_NAME, |
| 47 | quote(request.get_full_path()))) |
48 | quote(request.get_full_path()))) |
| 48 | 49 | ||
| 49 | return _check_valid_prefs |
50 | return _check_valid_prefs |
| /trunk/reviewboard/accounts/views.py | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| 1 |
|
1 |
|
| 2 | from sha import sha |
2 | from sha import sha |
| 3 | from django.conf import settings |
3 | from django.conf import settings |
| 4 | from django.contrib.auth import REDIRECT_FIELD_NAME |
4 | from django.contrib.auth import REDIRECT_FIELD_NAME |
| 5 | from django.contrib.auth.models import get_hexdigest |
5 | from django.contrib.auth.models import get_hexdigest |
| 6 | from django.core.urlresolvers import reverse |
||
| 6 | from django.http import HttpResponseRedirect |
7 | from django.http import HttpResponseRedirect |
| 7 | from django.shortcuts import render_to_response |
8 | from django.shortcuts import render_to_response |
| 8 | from django.template.context import RequestContext |
9 | from django.template.context import RequestContext |
| 9 | from djblets.auth.util import login_required |
10 | from djblets.auth.util import login_required |
| 10 | 11 | ||
| 11 | from reviewboard.accounts.forms import PreferencesForm |
12 | from reviewboard.accounts.forms import PreferencesForm |
| 12 | from reviewboard.accounts.models import Profile |
13 | from reviewboard.accounts.models import Profile |
| 13 | 14 | ||
| 14 | 15 | ||
| 15 | @login_required |
16 | @login_required |
| 16 | def user_preferences(request, template_name='accounts/prefs.html'): |
17 | def user_preferences(request, template_name='accounts/prefs.html'): |
| 17 | redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, settings.SITE_ROOT) |
18 | redirect_to = request.REQUEST.get(REDIRECT_FIELD_NAME, |
| 19 | reverse("dashboard")) |
||
| 18 | 20 | ||
| 19 | profile, profile_is_new = \ |
21 | profile, profile_is_new = \ |
| 20 | Profile.objects.get_or_create(user=request.user) |
22 | Profile.objects.get_or_create(user=request.user) |
| 21 | must_configure = not profile.first_time_setup_done |
23 | must_configure = not profile.first_time_setup_done |
| 22 | profile.save() |
24 | profile.save() |
| ... | 19 lines hidden [Expand] | ||
| 42 | profile.syntax_highlighting = \ |
44 | profile.syntax_highlighting = \ |
| 43 | form.cleaned_data['syntax_highlighting'] |
45 | form.cleaned_data['syntax_highlighting'] |
| 44 | profile.save() |
46 | profile.save() |
| 45 | 47 | ||
| 46 | return HttpResponseRedirect(redirect_to) |
48 | return HttpResponseRedirect(redirect_to) |
| 47 | |||
| 48 | else: |
49 | else: |
| 49 | form = PreferencesForm({ |
50 | form = PreferencesForm({ |
| 50 | 'settings': settings, |
51 | 'settings': settings, |
| 51 | 'redirect_to': redirect_to, |
52 | 'redirect_to': redirect_to, |
| 52 | 'first_name': request.user.first_name, |
53 | 'first_name': request.user.first_name, |
| ... | 11 lines hidden [Expand] | ||
| /trunk/reviewboard/reviews/datagrids.py | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| 1 |
|
1 |
|
| 2 | from django.contrib.auth.models import User |
2 | from django.contrib.auth.models import User |
| 3 | from django.core.urlresolvers import reverse |
||
| 3 | from django.db.models import Q |
4 | from django.db.models import Q |
| 4 | from django.template import Template |
5 | from django.template import Template |
| 5 | from django.template.context import RequestContext |
6 | from django.template.context import RequestContext |
| 6 | from django.template.defaultfilters import date, timesince |
7 | from django.template.defaultfilters import date, timesince |
| 7 | from django.template.loader import render_to_string |
8 | from django.template.loader import render_to_string |
| ... | 313 lines hidden [Expand] | ||
| 321 | "username", "fullname", "pending_reviews" |
322 | "username", "fullname", "pending_reviews" |
| 322 | ] |
323 | ] |
| 323 | 324 | ||
| 324 | @staticmethod |
325 | @staticmethod |
| 325 | def link_to_object(obj, value): |
326 | def link_to_object(obj, value): |
| 326 | return settings.SITE_ROOT + obj.get_absolute_url()[1:] |
327 | return reverse("user", obj) |
| 327 | 328 | ||
| 328 | 329 | ||
| 329 | class GroupDataGrid(DataGrid): |
330 | class GroupDataGrid(DataGrid): |
| 330 | """ |
331 | """ |
| 331 | A datagrid showing a list of review groups. |
332 | A datagrid showing a list of review groups. |
| ... | 29 lines hidden [Expand] | ||
| /trunk/reviewboard/reviews/urls.py | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| 1 |
|
1 |
|
| 2 | 2 | ||
| 3 | urlpatterns = patterns('reviewboard.reviews.views', |
3 | urlpatterns = patterns('reviewboard.reviews.views', |
| 4 | (r'^$', 'all_review_requests'), |
4 | url(r'^$', 'all_review_requests', name="all-review-requests"), |
| 5 | 5 | ||
| 6 | # Review request creation |
6 | # Review request creation |
| 7 | (r'^new/$', 'new_review_request'), |
7 | url(r'^new/$', 'new_review_request', name="new-review-request"), |
| 8 | 8 | ||
| 9 | # Review request detail |
9 | # Review request detail |
| 10 | (r'^(?P<review_request_id>[0-9]+)/$', 'review_detail', |
10 | (r'^(?P<review_request_id>[0-9]+)/$', 'review_detail', |
| 11 | {'template_name': 'reviews/review_detail.html'}), |
11 | {'template_name': 'reviews/review_detail.html'}), |
| 12 | 12 | ||
| ... | 36 lines hidden [Expand] | ||
| 49 | 'preview_review_email'), |
49 | 'preview_review_email'), |
| 50 | (r'^(?P<review_request_id>[0-9]+)/reviews/(?P<review_id>[0-9]+)/replies/(?P<reply_id>[0-9]+)/preview-email/$', |
50 | (r'^(?P<review_request_id>[0-9]+)/reviews/(?P<review_id>[0-9]+)/replies/(?P<reply_id>[0-9]+)/preview-email/$', |
| 51 | 'preview_reply_email'), |
51 | 'preview_reply_email'), |
| 52 | 52 | ||
| 53 | # Search |
53 | # Search |
| 54 | (r'^search/$', 'search'), |
54 | url(r'^search/$', 'search', name="search"), |
| 55 | ) |
55 | ) |
| /trunk/reviewboard/reviews/views.py | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| 1 |
|
1 |
|
| 2 | 2 | ||
| 3 | from django.conf import settings |
3 | from django.conf import settings |
| 4 | from django.contrib.auth.models import User |
4 | from django.contrib.auth.models import User |
| 5 | from django.contrib.sites.models import Site |
5 | from django.contrib.sites.models import Site |
| 6 | from django.core.urlresolvers import reverse |
||
| 6 | from django.db.models import Q |
7 | from django.db.models import Q |
| 7 | from django.http import HttpResponse, HttpResponseRedirect, Http404, \ |
8 | from django.http import HttpResponse, HttpResponseRedirect, Http404, \ |
| 8 | HttpResponseForbidden |
9 | HttpResponseForbidden |
| 9 | from django.shortcuts import get_object_or_404, render_to_response |
10 | from django.shortcuts import get_object_or_404, render_to_response |
| 10 | from django.template.context import RequestContext |
11 | from django.template.context import RequestContext |
| ... | 416 lines hidden [Expand] | ||
| 427 | # This should never happen under normal circumstances |
428 | # This should never happen under normal circumstances |
| 428 | raise Exception('Error when setting review status: unknown status code') |
429 | raise Exception('Error when setting review status: unknown status code') |
| 429 | 430 | ||
| 430 | review_request.save() |
431 | review_request.save() |
| 431 | if action == 'discard': |
432 | if action == 'discard': |
| 432 | return HttpResponseRedirect(settings.SITE_ROOT + 'dashboard/') |
433 | return HttpResponseRedirect(reverse("dashboard")) |
| 433 | else: |
434 | else: |
| 434 | return HttpResponseRedirect(review_request.get_absolute_url()) |
435 | return HttpResponseRedirect(review_request.get_absolute_url()) |
| 435 | 436 | ||
| 436 | 437 | ||
| 437 | @check_login_required |
438 | @check_login_required |
| ... | 128 lines hidden [Expand] | ||
| 566 | # FIXME: show something useful |
567 | # FIXME: show something useful |
| 567 | raise Http404 |
568 | raise Http404 |
| 568 | 569 | ||
| 569 | if not query: |
570 | if not query: |
| 570 | # FIXME: I'm not super thrilled with this |
571 | # FIXME: I'm not super thrilled with this |
| 571 | return HttpResponseRedirect('/') |
572 | return HttpResponseRedirect(reverse("root")) |
| 572 | 573 | ||
| 573 | import lucene |
574 | import lucene |
| 574 | 575 | ||
| 575 | # We may have already initialized lucene |
576 | # We may have already initialized lucene |
| 576 | try: |
577 | try: |
| ... | 26 lines hidden [Expand] | ||
| /trunk/reviewboard/reviews/templatetags/reviewtags.py | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| ... | 317 lines hidden [Expand] | ||
| 318 | else: |
318 | else: |
| 319 | count = len(review_requests) |
319 | count = len(review_requests) |
| 320 | 320 | ||
| 321 | return { |
321 | return { |
| 322 | 'MEDIA_URL': settings.MEDIA_URL, |
322 | 'MEDIA_URL': settings.MEDIA_URL, |
| 323 | 'SITE_ROOT': settings.SITE_ROOT, |
||
| 324 | 'level': level, |
323 | 'level': level, |
| 325 | 'text': text, |
324 | 'text': text, |
| 326 | 'view': view, |
325 | 'view': view, |
| 327 | 'group': group, |
326 | 'group': group, |
| 328 | 'count': count, |
327 | 'count': count, |
| ... | 155 lines hidden [Expand] | ||
| 484 | return render_to_string('reviews/star.html', { |
483 | return render_to_string('reviews/star.html', { |
| 485 | 'object': obj_info, |
484 | 'object': obj_info, |
| 486 | 'starred': int(starred), |
485 | 'starred': int(starred), |
| 487 | 'user': user, |
486 | 'user': user, |
| 488 | 'MEDIA_URL': settings.MEDIA_URL, |
487 | 'MEDIA_URL': settings.MEDIA_URL, |
| 489 | 'SITE_ROOT': settings.SITE_ROOT, |
||
| 490 | }) |
488 | }) |
| /trunk/reviewboard/templates/base.html | |||
|---|---|---|---|
| Revision 1295 | New Change | ||
| ... | 30 lines hidden [Expand] | ||
| 31 | {% block bodytag %} |
31 | {% block bodytag %} |
| 32 | <body> |
32 | <body> |