Ticket #13588: admin_urlresolvers.diff

File admin_urlresolvers.diff, 23.8 KB (added by Florian Apolloner, 14 years ago)
  • django/contrib/admin/actions.py

    diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py
    index b75c91b..e98d4eb 100644
    a b def delete_selected(modeladmin, request, queryset):  
    3232
    3333    # Populate deletable_objects, a data structure of all related objects that
    3434    # will also be deleted.
    35     deletable_objects, perms_needed = get_deleted_objects(queryset, opts, request.user, modeladmin.admin_site, levels_to_root=2)
     35    deletable_objects, perms_needed = get_deleted_objects(queryset, opts, request.user, modeladmin.admin_site)
    3636
    3737    # The user has already confirmed the deletion.
    3838    # Do the deletion and return a None to display the change list view again.
  • django/contrib/admin/options.py

    diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
    index 1f8ff6d..af6a0eb 100644
    a b from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_ob  
    99from django.contrib import messages
    1010from django.views.decorators.csrf import csrf_protect
    1111from django.core.exceptions import PermissionDenied, ValidationError
     12from django.core.urlresolvers import reverse
    1213from django.db import models, transaction
    1314from django.db.models.fields import BLANK_CHOICE_DASH
    1415from django.http import Http404, HttpResponse, HttpResponseRedirect
    class BaseModelAdmin(object):  
    145146        """
    146147        db = kwargs.get('using')
    147148        if db_field.name in self.raw_id_fields:
    148             kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, using=db)
     149            kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel, self.admin_site,
     150                                                             using=db)
    149151        elif db_field.name in self.radio_fields:
    150152            kwargs['widget'] = widgets.AdminRadioSelect(attrs={
    151153                'class': get_ul_class(self.radio_fields[db_field.name]),
    class BaseModelAdmin(object):  
    165167        db = kwargs.get('using')
    166168
    167169        if db_field.name in self.raw_id_fields:
    168             kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, using=db)
     170            kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel, self.admin_site,
     171                                                             using=db)
    169172            kwargs['help_text'] = ''
    170173        elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
    171174            kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
    class ModelAdmin(BaseModelAdmin):  
    663666            # redirect to the change-list page for this object. Otherwise,
    664667            # redirect to the admin index.
    665668            if self.has_change_permission(request, None):
    666                 post_url = '../'
     669                post_url = reverse('admin:%s_%s_changelist' %
     670                                   (opts.app_label, opts.module_name),
     671                                   current_app=self.admin_site.name)
    667672            else:
    668                 post_url = '../../../'
     673                post_url = reverse('admin:index',
     674                                   current_app=self.admin_site.name)
    669675            return HttpResponseRedirect(post_url)
    670676
    671677    def response_change(self, request, obj):
    class ModelAdmin(BaseModelAdmin):  
    685691        elif request.POST.has_key("_saveasnew"):
    686692            msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj}
    687693            self.message_user(request, msg)
    688             return HttpResponseRedirect("../%s/" % pk_value)
     694            return HttpResponseRedirect(reverse('admin:%s_%s_change' %
     695                                        (opts.app_label, opts.module_name),
     696                                        args=pk_value,
     697                                        current_app=self.admin_site.name))
     698
    689699        elif request.POST.has_key("_addanother"):
    690700            self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
    691             return HttpResponseRedirect("../add/")
     701            return HttpResponseRedirect(reverse('admin:%s_%s_add' %
     702                                        (opts.app_label, opts.module_name),
     703                                        current_app=self.admin_site.name))
    692704        else:
    693705            self.message_user(request, msg)
    694             return HttpResponseRedirect("../")
     706            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
     707                                        (opts.app_label, opts.module_name),
     708                                        current_app=self.admin_site.name))
    695709
    696710    def response_action(self, request, queryset):
    697711        """
    class ModelAdmin(BaseModelAdmin):  
    866880            raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_unicode(opts.verbose_name), 'key': escape(object_id)})
    867881
    868882        if request.method == 'POST' and request.POST.has_key("_saveasnew"):
    869             return self.add_view(request, form_url='../add/')
     883            return self.add_view(request, form_url=reverse('admin:%s_%s_add' %
     884                                    (opts.app_label, opts.module_name),
     885                                    current_app=self.admin_site.name))
    870886
    871887        ModelForm = self.get_form(request, obj)
    872888        formsets = []
    class ModelAdmin(BaseModelAdmin):  
    11181134            self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)})
    11191135
    11201136            if not self.has_change_permission(request, None):
    1121                 return HttpResponseRedirect("../../../../")
    1122             return HttpResponseRedirect("../../")
     1137                return HttpResponseRedirect(reverse('admin:index',
     1138                                                    current_app=self.admin_site.name))
     1139            return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
     1140                                        (opts.app_label, opts.module_name),
     1141                                        current_app=self.admin_site.name))
    11231142
    11241143        context = {
    11251144            "title": _("Are you sure?"),
  • django/contrib/admin/sites.py

    diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
    index 4446490..e451f8f 100644
    a b class AdminSite(object):  
    456456            context_instance=context_instance
    457457        )
    458458
    459     def root(self, request, url):
    460         """
    461         DEPRECATED. This function is the old way of handling URL resolution, and
    462         is deprecated in favor of real URL resolution -- see ``get_urls()``.
    463 
    464         This function still exists for backwards-compatibility; it will be
    465         removed in Django 1.3.
    466         """
    467         import warnings
    468         warnings.warn(
    469             "AdminSite.root() is deprecated; use include(admin.site.urls) instead.",
    470             DeprecationWarning
    471         )
    472 
    473         #
    474         # Again, remember that the following only exists for
    475         # backwards-compatibility. Any new URLs, changes to existing URLs, or
    476         # whatever need to be done up in get_urls(), above!
    477         #
    478 
    479         if request.method == 'GET' and not request.path.endswith('/'):
    480             return http.HttpResponseRedirect(request.path + '/')
    481 
    482         if settings.DEBUG:
    483             self.check_dependencies()
    484 
    485         # Figure out the admin base URL path and stash it for later use
    486         self.root_path = re.sub(re.escape(url) + '$', '', request.path)
    487 
    488         url = url.rstrip('/') # Trim trailing slash, if it exists.
    489 
    490         # The 'logout' view doesn't require that the person is logged in.
    491         if url == 'logout':
    492             return self.logout(request)
    493 
    494         # Check permission to continue or display login form.
    495         if not self.has_permission(request):
    496             return self.login(request)
    497 
    498         if url == '':
    499             return self.index(request)
    500         elif url == 'password_change':
    501             return self.password_change(request)
    502         elif url == 'password_change/done':
    503             return self.password_change_done(request)
    504         elif url == 'jsi18n':
    505             return self.i18n_javascript(request)
    506         # URLs starting with 'r/' are for the "View on site" links.
    507         elif url.startswith('r/'):
    508             from django.contrib.contenttypes.views import shortcut
    509             return shortcut(request, *url.split('/')[1:])
    510         else:
    511             if '/' in url:
    512                 return self.model_page(request, *url.split('/', 2))
    513             else:
    514                 return self.app_index(request, url)
    515 
    516         raise http.Http404('The requested admin page does not exist.')
    517 
    518     def model_page(self, request, app_label, model_name, rest_of_url=None):
    519         """
    520         DEPRECATED. This is the old way of handling a model view on the admin
    521         site; the new views should use get_urls(), above.
    522         """
    523         from django.db import models
    524         model = models.get_model(app_label, model_name)
    525         if model is None:
    526             raise http.Http404("App %r, model %r, not found." % (app_label, model_name))
    527         try:
    528             admin_obj = self._registry[model]
    529         except KeyError:
    530             raise http.Http404("This model exists but has not been registered with the admin site.")
    531         return admin_obj(request, rest_of_url)
    532     model_page = never_cache(model_page)
    533459
    534460# This global object represents the default admin site, for the common case.
    535461# You can instantiate AdminSite in your own code to create a custom admin site.
  • django/contrib/admin/util.py

    diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
    index 776a6f0..d6d1b22 100644
    a b from django.utils.safestring import mark_safe  
    77from django.utils.text import capfirst
    88from django.utils.encoding import force_unicode, smart_unicode, smart_str
    99from django.utils.translation import ungettext, ugettext as _
    10 from django.core.urlresolvers import reverse, NoReverseMatch
     10from django.core.urlresolvers import reverse
    1111from django.utils.datastructures import SortedDict
    1212
    1313def quote(s):
    def flatten_fieldsets(fieldsets):  
    5858                field_names.append(field)
    5959    return field_names
    6060
    61 def _format_callback(obj, user, admin_site, levels_to_root, perms_needed):
     61def _format_callback(obj, user, admin_site, perms_needed):
    6262    has_admin = obj.__class__ in admin_site._registry
    6363    opts = obj._meta
    64     try:
     64    if has_admin:
    6565        admin_url = reverse('%s:%s_%s_change'
    6666                            % (admin_site.name,
    6767                               opts.app_label,
    6868                               opts.object_name.lower()),
    6969                            None, (quote(obj._get_pk_val()),))
    70     except NoReverseMatch:
    71         admin_url = '%s%s/%s/%s/' % ('../'*levels_to_root,
    72                                      opts.app_label,
    73                                      opts.object_name.lower(),
    74                                      quote(obj._get_pk_val()))
    75     if has_admin:
    7670        p = '%s.%s' % (opts.app_label,
    7771                       opts.get_delete_permission())
    7872        if not user.has_perm(p):
    def _format_callback(obj, user, admin_site, levels_to_root, perms_needed):  
    8882        return u'%s: %s' % (capfirst(opts.verbose_name),
    8983                            force_unicode(obj))
    9084
    91 def get_deleted_objects(objs, opts, user, admin_site, levels_to_root=4):
     85def get_deleted_objects(objs, opts, user, admin_site):
    9286    """
    9387    Find all objects related to ``objs`` that should also be
    9488    deleted. ``objs`` should be an iterable of objects.
    9589
    9690    Returns a nested list of strings suitable for display in the
    9791    template with the ``unordered_list`` filter.
    98 
    99     `levels_to_root` defines the number of directories (../) to reach
    100     the admin root path. In a change_view this is 4, in a change_list
    101     view 2.
    102 
    103     This is for backwards compatibility since the options.delete_selected
    104     method uses this function also from a change_list view.
    105     This will not be used if we can reverse the URL.
    10692    """
    10793    collector = NestedObjects()
    10894    for obj in objs:
    def get_deleted_objects(objs, opts, user, admin_site, levels_to_root=4):  
    114100    to_delete = collector.nested(_format_callback,
    115101                                 user=user,
    116102                                 admin_site=admin_site,
    117                                  levels_to_root=levels_to_root,
    118103                                 perms_needed=perms_needed)
    119104
    120105    return to_delete, perms_needed
  • django/contrib/admin/widgets.py

    diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
    index 1d321d0..0c65321 100644
    a b from django.utils.translation import ugettext as _  
    1313from django.utils.safestring import mark_safe
    1414from django.utils.encoding import force_unicode
    1515from django.conf import settings
    16 from django.core.urlresolvers import reverse, NoReverseMatch
     16from django.core.urlresolvers import reverse
    1717
    1818class FilteredSelectMultiple(forms.SelectMultiple):
    1919    """
    class ForeignKeyRawIdWidget(forms.TextInput):  
    105105    A Widget for displaying ForeignKeys in the "raw_id" interface rather than
    106106    in a <select> box.
    107107    """
    108     def __init__(self, rel, attrs=None, using=None):
     108    def __init__(self, rel, admin_site, attrs=None, using=None):
    109109        self.rel = rel
    110110        self.db = using
     111        self.admin_site = admin_site
    111112        super(ForeignKeyRawIdWidget, self).__init__(attrs)
    112113
    113114    def render(self, name, value, attrs=None):
     115        rel_to = self.rel.to
    114116        if attrs is None:
    115117            attrs = {}
    116         related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower())
    117         params = self.url_parameters()
    118         if params:
    119             url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
    120         else:
    121             url = ''
    122         if not attrs.has_key('class'):
    123             attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
    124         output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)]
    125         # TODO: "id_" is hard-coded here. This should instead use the correct
    126         # API to determine the ID dynamically.
    127         output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
    128             (related_url, url, name))
    129         output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
     118
     119        extra = []
     120        if rel_to in self.admin_site._registry: # If the related object has an admin interface:
     121            related_url = reverse('admin:%s_%s_changelist' % (rel_to._meta.app_label, rel_to._meta.module_name))
     122            params = self.url_parameters()
     123            if params:
     124                url = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
     125            else:
     126                url = ''
     127            if not attrs.has_key('class'):
     128                attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook.
     129
     130            # TODO: "id_" is hard-coded here. This should instead use the correct
     131            # API to determine the ID dynamically.
     132            extra.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \
     133                (related_url, url, name))
     134            extra.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup')))
     135
     136        output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra
    130137        if value:
    131138            output.append(self.label_for_value(value))
    132139        return mark_safe(u''.join(output))
    class ManyToManyRawIdWidget(ForeignKeyRawIdWidget):  
    163170    A Widget for displaying ManyToMany ids in the "raw_id" interface rather than
    164171    in a <select multiple> box.
    165172    """
    166     def __init__(self, rel, attrs=None, using=None):
    167         super(ManyToManyRawIdWidget, self).__init__(rel, attrs, using=None)
     173    def __init__(self, rel, admin_site, attrs=None, using=None):
     174        super(ManyToManyRawIdWidget, self).__init__(rel, admin_site, attrs, using=None)
    168175
    169176    def render(self, name, value, attrs=None):
    170177        attrs['class'] = 'vManyToManyRawIdAdminField'
    class RelatedFieldWidgetWrapper(forms.Widget):  
    229236    def render(self, name, value, *args, **kwargs):
    230237        rel_to = self.rel.to
    231238        info = (rel_to._meta.app_label, rel_to._meta.object_name.lower())
    232         try:
    233             related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
    234         except NoReverseMatch:
    235             info = (self.admin_site.root_path, rel_to._meta.app_label, rel_to._meta.object_name.lower())
    236             related_url = '%s%s/%s/add/' % info
    237239        self.widget.choices = self.choices
    238240        output = [self.widget.render(name, value, *args, **kwargs)]
    239241        if rel_to in self.admin_site._registry: # If the related object has an admin interface:
     242            related_url = reverse('admin:%s_%s_add' % info, current_app=self.admin_site.name)
    240243            # TODO: "id_" is hard-coded here. This should instead use the correct
    241244            # API to determine the ID dynamically.
    242245            output.append(u'<a href="%s" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \
  • tests/regressiontests/admin_inlines/models.py

    diff --git a/tests/regressiontests/admin_inlines/models.py b/tests/regressiontests/admin_inlines/models.py
    index 5a12e07..2998373 100644
    a b class BookInline(admin.TabularInline):  
    4343class AuthorAdmin(admin.ModelAdmin):
    4444    inlines = [BookInline]
    4545
     46admin.site.register(Book)
    4647admin.site.register(Author, AuthorAdmin)
    4748
    4849class Holder(models.Model):
  • tests/regressiontests/admin_views/tests.py

    diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
    index 1385e5e..f60bb04 100644
    a b class SaveAsTests(TestCase):  
    324324        self.assert_(response.context['save_as'])
    325325        post_data = {'_saveasnew':'', 'name':'John M', 'gender':3, 'alive':'checked'}
    326326        response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
    327         self.assertEqual(response.context['form_url'], '../add/')
     327        self.assertEqual(response.context['form_url'], '/test_admin/admin/admin_views/person/add/')
    328328
    329329class CustomModelAdminTest(AdminViewBasicTest):
    330330    urlbit = "admin2"
  • tests/regressiontests/admin_widgets/models.py

    diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
    index 59d625b..f301f13 100644
    a b from django.conf import settings  
    33from django.db import models
    44from django.core.files.storage import default_storage
    55from django.contrib.auth.models import User
     6from django.contrib import admin
    67
    7 class MyFileField(models.FileField): 
     8class MyFileField(models.FileField):
    89    pass
    910
    1011class Member(models.Model):
    class CarTire(models.Model):  
    7071    """
    7172    car = models.ForeignKey(Car)
    7273
     74admin.site.register(Inventory)
     75admin.site.register(Member)
     76admin.site.register(Band)
     77
    7378__test__ = {'WIDGETS_TESTS': """
    7479>>> from datetime import datetime
    7580>>> from django.utils.html import escape, conditional_escape
    Currently: <a target="_blank" href="%(STORAGE_URL)salbums/hybrid_theory.jpg">alb  
    121126<input type="file" name="test" />
    122127
    123128>>> rel = Album._meta.get_field('band').rel
    124 >>> w = ForeignKeyRawIdWidget(rel)
     129>>> w = ForeignKeyRawIdWidget(rel, admin.site)
    125130>>> print conditional_escape(w.render('test', band.pk, attrs={}))
    126 <input type="text" name="test" value="1" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Linkin Park</strong>
     131<input type="text" name="test" value="1" class="vForeignKeyRawIdAdminField" /><a href="/test_admin/admin/admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Linkin Park</strong>
    127132
    128133>>> m1 = Member.objects.create(pk=1, name='Chester')
    129134>>> m2 = Member.objects.create(pk=2, name='Mike')
    130135>>> band.members.add(m1, m2)
    131136
    132137>>> rel = Band._meta.get_field('members').rel
    133 >>> w = ManyToManyRawIdWidget(rel)
     138>>> w = ManyToManyRawIdWidget(rel, admin.site)
    134139>>> print conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={}))
    135 <input type="text" name="test" value="1,2" class="vManyToManyRawIdAdminField" /><a href="../../../admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>
     140<input type="text" name="test" value="1,2" class="vManyToManyRawIdAdminField" /><a href="/test_admin/admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>
    136141>>> w._has_changed(None, None)
    137142False
    138143>>> w._has_changed([], None)
    True  
    152157>>> pear = Inventory.objects.create(barcode=22, name='Pear')
    153158>>> core = Inventory.objects.create(barcode=87, name='Core', parent=apple)
    154159>>> rel = Inventory._meta.get_field('parent').rel
    155 >>> w = ForeignKeyRawIdWidget(rel)
     160>>> w = ForeignKeyRawIdWidget(rel, admin.site)
    156161>>> print w.render('test', core.parent_id, attrs={})
    157 <input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Apple</strong>
     162<input type="text" name="test" value="86" class="vForeignKeyRawIdAdminField" /><a href="/test_admin/admin/admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Apple</strong>
    158163
    159164# see #9258
    160165>>> hidden = Inventory.objects.create(barcode=93, name='Hidden', hidden=True)
    161166>>> child_of_hidden = Inventory.objects.create(barcode=94, name='Child of hidden', parent=hidden)
    162167>>> print w.render('test', child_of_hidden.parent_id, attrs={})
    163 <input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="../../../admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Hidden</strong>
     168<input type="text" name="test" value="93" class="vForeignKeyRawIdAdminField" /><a href="/test_admin/admin/admin_widgets/inventory/?t=barcode" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_MEDIA_PREFIX)simg/admin/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Hidden</strong>
    164169""" % {
    165170    'ADMIN_MEDIA_PREFIX': settings.ADMIN_MEDIA_PREFIX,
    166171    'STORAGE_URL': default_storage.url(''),
Back to Top