Django 1.6 reverse() fails, kwarg value missing from Exception value - python

I have an affiliate program app with redirects and link statistics tracking. A read-only field in the Django Admin should display the full URL of the affiliate link, (so the user can copy+paste this into their editor) but the reverse() call in the model instance's get_absolute_url() method is failing when called from a callable in the admin class. For example:
from project urls.py
urlpatterns = patterns(
'',
url(r'^a/', include('shop.affiliates.urls', namespace='affiliates')),
...
from shop.affiliates.urls.py
urlpatterns = patterns(
'',
url(r'^(?P<slug>[\w]+)/$', redirect_to_affiliate_link, name='affiliate_redirect'),
)
from shop.affiliates.models.py
class AffiliateLink(models.Model):
...
slug = models.SlugField(
max_length=4,
help_text='The slug for this link, used to generate url.',
)
...
def get_absolute_url(self):
return reverse(
'affiliates:affiliate_redirect',
kwargs={'slug': self.slug},
)
Note: for the sake of debugging simplicity, it's safe to assume that the slug field has already been populated in the database previously. So I'm not trying to create a new object where slug has not yet been set.
from shop.affiliates.admin.py
class AffiliateLinkInline(admin.StackedInline):
model = AffiliateLink
extra = 0
fields = (
..., 'hyperlink', ...
)
readonly_fields = (
'hyperlink', ...
)
def hyperlink(self, obj):
url = 'http://example.com{}'.format(obj.get_absolute_url())
return '{0}'.format(url)
hyperlink.allow_tags = True
When loading the appropriate admin page, I get a NoReverseMatch exception.
Exception value:
Reverse for 'affiliate_redirect' with arguments '()' and keyword arguments '{u'slug': u''}' not found. 1 pattern(s) tried: [u'a/(?P<slug>[\\w]+)/$']
so the proper regex is found, but the slug parameter is empty. I verified that obj.slug within the admin callable exists, and it is the correct AffiliateLink slug. To make matters more strange, if I switch the kwargs in get_absolute_url() like so:
def get_absolute_url(self):
return reverse(
'affiliates:affiliate_redirect',
kwargs={'bug': self.slug},
)
Then the exception value changes to:
Reverse for 'affiliate_redirect' with arguments '()' and keyword arguments '{u'bug': u'7aeB'}' not found. 1 pattern(s) tried: [u'a/(?P<slug>[\\w]+)/$']
So the kwarg value for key 'slug' disappears from the exception value, but the value for 'bug' stays.
What am I doing wrong here? Any help is greatly appreciated.

URL patterns are sometime evaluated before URLconf has been loaded. Try reverse_lazy instead of reverse
https://docs.djangoproject.com/en/dev/ref/urlresolvers/#reverse-lazy

Related

Django list view repeatedly fails, but detail pages work

testt1
is my view for taking a queryset and turning out a list of objects and their foreign key children.
mydetail
is the detail view for the individual objects. No slug is used in the list, but it is in the list template to call the detail pages. But all these errors come from trying to call the list, not the detail. If I just type the slug into the address bar, all my detail pages come up just fine!
I have tried a lot of different things, and get a lot of different errors, but the bottom line is always the same: no list
named url fails
Error during template rendering
In template /home/malikarumi/Projects/hattie/templates/base.html, error at line 44
Reverse for 'jhp_url' not found. 'jhp_url' is not a valid view function or pattern
name.
‘jhp_url’ is the named url for the detail page.
But here is line 44:
<link href="{% static 'plugins/owl-carousel/owl.carousel.css' %}" rel="stylesheet">
the urlpattern:
path('<slug:slug>/', mydetail, name='jhp_url'),
the call to reverse():
def get_absolute_url(self):
return reverse(‘jhp_url', kwargs={'slug': self.slug})
namespaced url fails
Error during template rendering
In template /home/malikarumi/Projects/hattie/templates/base.html, error at line 44
Reverse for 'jhp_url' with keyword arguments '{'slug': ''}' not found. 1 pattern(s) tried: ['(?P[^/]+)/courts/(?P[-a-zA-Z0-9_]+)/$']
Now it has an empty string for the slug. The pattern is right. If it had a slug, it would work.
def get_absolute_url(self):
return reverse('bench:jhp_url', kwargs={'slug': self.slug})
list template:
<h3>{{ C.name }}</h3>
urlpattern is the same.
no slug passed
testt1() missing 1 required positional argument: 'slug'
Which means that as I have currently defined it:
def testt1(request, slug, *args, **kwargs):
print(request.slug)
Is not being provided the slug variable / value. Now it might also be a question of position, so I changed only this:
def testt1(request, *args, **kwargs):
But since slug is still in the return value, now I get:
Unresolved reference 'slug'
I take it out, and the autoreloader wakes up…
'WSGIRequest' object has no attribute 'slug'
Yes, well, that's what I suspected. That’s why I had the print in there. The question is: why does it not have slug?
https://docs.djangoproject.com/en/3.2/topics/http/urls/#including-other-urlconfs
Whenever Django encounters include(), it chops off whatever part of the URL matched up to that point and sends the remaining string to the included URLconf for further processing.
Apparently, I don’t understand this process, because I always assumed this meant the slug part of my url was being passed along as part of the request object. But now I see that the word 'slug' does not appear in the docs on the response request page:
https://docs.djangoproject.com/en/dev/ref/request-response/
extra arguments fail
The docs also give the option of passing ‘extra arguments’:
https://docs.djangoproject.com/en/3.2/topics/http/urls/#passing-extra-options-to-include
https://docs.djangoproject.com/en/3.2/topics/http/urls/#passing-extra-options-to-view-functions
But this says nothing about using extra arguments in conjunction with named or even namespaced urls. Does that mean they don't, or can't go together? Then shouldn’t they say so explicitly, like the Zen of Python? Anyway, my efforts in this area all failed miserably:
File "/home/malikarumi/Projects/hattie/bench/urls.py", line 13
path('<slug:slug>', mydetail, name='jhp_url', {'slug': slug}),
^
SyntaxError: positional argument follows keyword argument
Are they saying a dictionary is a positional argument, and not a keyword?
File "/home/malikarumi/Projects/hattie/bench/urls.py", line 14, in <module>
path('', testt1, slug='slug', name='testt1'),
TypeError: _path() got an unexpected keyword argument 'slug'
File "/home/malikarumi/Projects/hattie/bench/urls.py", line 13, in <module>
path('<slug:slug>', mydetail, slug='slug', name='jhp_url'),
TypeError: _path() got an unexpected keyword argument 'slug'
File "/home/malikarumi/Projects/hattie/bench/urls.py", line 13, in <module>
path('<slug:slug>', mydetail, name='jhp_url', slug=slug),
NameError: name 'slug' is not defined
It's not? then wtf is 'slug:slug'???
Clearly, the slug is not being passed. If it isn't happening automatically,
and it isn't part of the request object - despite slug:slug in the
urlpattern, and I can't use these extra arguments, how am I supposed to get
the slug passed?!
explicit slug keyword argument in view fails:
File "/home/malikarumi/Projects/hattie/bench/views.py", line 22
def testt1(request, *args, '<slug:slug>', **kwargs):
^
SyntaxError: invalid syntax
Just in case you wondering, yes, I did try explicitly passing the slug as a keyword. All such efforts failed.
def testt1(request, slug=slug, **kwargs):
unresolved reference slug
def testt1(request, slug=self.slug, **kwargs):
unresolved reference self
UPDATE
As requested, here is my latest version of views and urls, and at the bottom a link to the full stacktrace going back to yesterday.
VIEWS
from django.shortcuts import render
from bench.models import Jurisdiction
from django.shortcuts import get_object_or_404
from statedict import sdl
j = Jurisdiction.objects.all()
from urllib.parse import urlparse
def mydetail(request, slug, **kwargs):
court = j.get(slug=slug)
# twodigit = twodigit
return render(request, 'bench/jhp_.html',
{'court': court})
def testt1(request, **kwargs):
federalcourts = j.filter(sphere="Appeals").filter(purview="Federal")
circuits = federalcourts.order_by('siblingrank')
scotus = circuits.first().name
context = {'circuits': circuits, 'scotus': scotus, 'slug': slug}
return render(request, 'bench/listtest1.html', context)
URLS
from django.urls import path
from bench.views import mydetail, testt1
app_name = 'bench'
urlpatterns = [
path('/', mydetail, name='jhp_url'),
path('', testt1, name='testt1'),
]
STACKTRACE:
https://docs.google.com/document/d/1ktm-y_AEUrllw0kSctlLgUyNfxVx6eFnYlOZCs-jM2w/edit?usp=sharing
thank you
I would drop the **kwargs from your views unless you have a good reason not to. The reason is that it will hide problems with your views and urls.
The way I would write your views and urls are as follows:
# views.py
def mydetail(request, slug):
...
def testt1(request):
...
# urls.py
app_name = 'bench'
urlpatterns = [
path('<slug:slug>/', mydetail, name='jhp_url'),
path('/', testt1, name='testt1'),
]
The fully qualified url names for these would be:
bench:jhp_url
bench:testt1
If you're using something like:
<h3>{{ C.name }}</h3>
And it's still giving you an error about Reverse for 'jhp_url' with keyword arguments '{'slug': ''}' not found., then the issue is that C.slug is resolving to an empty string or None, or C is None.

Django Admin Model on add fails to render related change link

Given following admin settings:
class BrokerLocationSetForm(forms.ModelForm):
class Meta:
model = BrokerLocationSet
fields = ('broker', 'program', 'label', 'locations')
widgets = {
'locations': autocomplete.ModelSelect2Multiple(url='admin-autocomplete-location', forward=('broker','program')),
}
class BrokerLocationSetAdmin(admin.ModelAdmin):
model = BrokerLocationSet
form = BrokerLocationSetForm
list_display=['broker', 'program', 'label']
admin.site.register(BrokerLocationSet, BrokerLocationSetAdmin)
When I try navigate to add view in admin for BrokerLocationSetForm it raises following error:
raise NoReverseMatch(msg) NoReverseMatch: Reverse for 'program_program_change' with arguments '(u'__fk__',)' not found. 1 pattern(s) tried: [u'admin/program/program/(?P<program_pk>\\d+)/change/$']
When I debug in shell:
reverse('admin:broker_broker_change', 'myapp.urls', args=(u'__fk__',))
it outputs:
u'/admin/broker/broker/fk/change/'
but for:
reverse('admin:program_program_change', 'myapp.urls', args=(u'__fk__',))
I get same error as above. After some debugging I sensed that somehow admin was passing a string instead of an int into reverse function while it expected an integer as below :
reverse('admin:program_program_change', 'myapp.urls', args=(u'1',))
u'/admin/program/program/1/change/'
Since django admin does this url reversing magic I am not sure where I should customize this to fix the bug. I have got this code base fairly new and to get sense completely.
How I can fix above bug by customizing admin model or form. I dont want to update 'admin:program_program_change' but probably provide an alternate route to same view! . Is it possible ? please advise !
I found a solution however, I am not sure if this is best one. Since ProgramAdmin expects a numeric parameter while popup link from BrokerLocationSetAdmin is expecting a route with a string parameter. e.g
reverse('admin:program_program_change', 'myapp.urls', args=(u'__fk__',))
Solution was to inject another admin route with same name to ProgramAdmin model by overriding its get_urls method as follow:
class ProgramAdmin(admin.ModelAdmin):
...
...
def get_urls(self):
from django.conf.urls import url
from functools import update_wrapper
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
urls = super(ProgramAdmin, self).get_urls()
info = self.model._meta.app_label, self.model._meta.model_name
alt_urls=[
url(r'^(?P<program_pk>\w+)/change/$', wrap(self.change_view), name='%s_%s_change' % info),
]
return urls+alt_urls
Now we have two routes with same name but different paths parameter e.g:
/admin/program/program/<program_pk>/change/ django.contrib.admin.options.change_view admin:program_program_change
admin/program/program/(?P\d+)/change/$
/admin/program/program/<program_pk>/change/ django.contrib.admin.options.change_view admin:program_program_change
admin/program/program/(?P\w+)/change/$
Depending on the context one of route will be used.

Django Redirect - URL with String to URL With Slug

Not sure if I should be doing this or not... but here goes.
I have a url that looks like this:
/deals/category/Apparel/
I changed it to category/Apparel to shorten it.
but also notice the capitalized Apparel --as it is using the category name.
So I added a slug to my Category model and I'm trying to redirect the
deals/category/Apparel to category/apparel where the latter represents the slug
In my deals app I have this URL:
path('category/<str:category>', RedirectView.as_view(pattern_name='category', permanent=True)),
and which I'm trying to redirect to (in my core urls file)
path('category/<slug:slug>', deals_by_category, name='category')
My view for the `deals_by_category' looks like this:
def deals_by_category(request,slug):
category_deals = Deal.objects.filter(category__slug=slug).order_by('expired','-date_added')
category = category_deals[0].category
return render(request, 'deals/category.html', {'category_deals': category_deals, 'category':category})
so when I go to deals/category/Apparel it is redirecting to category/Apparel which is not what I want... and I get an error like this:
Reverse for 'category' with keyword arguments '{'category': 'Apparel'}' not found. 1 pattern(s) tried: ['category\\/(?P<slug>[-a-zA-Z0-9_]+)$']
I guess I understand that it is looking at the category name and trying to match against a slug, but not exactly sure how to correctly redirect this with the correct format.
path('category/<str:category>', RedirectView.as_view(pattern_name='category', permanent=True)),
When you use pattern_name, Django will try to reverse the URL with the same args and kwargs, in this case category='Apparel'.
If you want to use the slug in the URL instead, then you'll have to subclass RedirectView and override get_redirect_url.
from django.shortcuts import get_object_or_404
class CategoryRedirectView(RedirectView):
permanent = True
def get_redirect_url(self, *args, **kwargs):
category = get_object_or_404(Category, name=self.kwargs['category'])
return reverse('category', kwargs={'slug': category.slug})
Then use your view in your URL pattern:
path('category/<slug:slug>', CategoryRedirectView.as_view(), name='category')
I wouldn't set permanent = True until you are sure that the redirect is working as expected. Otherwise browsers may cache incorrect redirects.

Django: reverse() does not find view with args/kwargs given

There are a few posts related to my problem. However, none seems to solve the following issue:
I have defined urls in myapp/urls.py:
app_name = "myapp"
urlpatterns=[
url(r'^$', views.index, name="index"),
url(r'^adduser/$', views.addUser, name="adduser"),
]
and corresponding views in myapp/views.py:
def index(request, status_msg=None, error_msg=None):
return HttpResponse(render(request, "myapp/index.html",
context={"error_message":error_msg, "status_message":status_msg}))
def addUser(request):
try:
uname=request.POST["user_name"]
except KeyError:
url = reverse("myapp:index", args=(None, "No user name specified."))
return HttpResponseRedirect(url)
# ... adding user to db ...
url = reverse("myapp:index", args=("Added user '%s'"%uname,))
return HttpResponseRedirect(url)
Either variant of passing arguments to index() from the redirect in addUser() yields an error of the kind
Reverse for 'index' with arguments '(None, u'No user name specified.')' not found. 1 pattern(s) tried: [u'myapp/$']
I don't get what I'm doing wrong here. Seems the index view is found but not recognized to allow any arguments aside the request?
Using kwargs instead of args does not help, either.
Any suggestions are highly appreciated. Thanks already!
Your URL doesn't seem to support arguments (i.e captured groups in the regex). Therefore you should be using just:
reverse("myapp:index")
Check the docs for the reverse() function here.

Passing kwargs to Django URL and views

I would like to pass kwargs to my view function through URL.
urls.py
urlpatterns = [
# ------------- set relations --------------------
url(r'^approve-form/$', views.approve_form,
{'content_type':None, 'form_id':None,}, name='approve-form'),]
views.py
def approve_form(request, content_type=None, form_id=None):
return HttpResponse('Working')
Now I am using reverse_lazy function to call the url from on of the model instance
models.py
class FormStatus(models.Model):
content_type = models.ForeignKey(ContentType)
form_id = models.IntegerField(verbose_name='Form Ref ID')
def __str__(self):
return "{}".format(self.content_type)
def get_approve_link(self):
return reverse_lazy("flow-control:approve-form", kwargs={'form_id':self.form_id,
'content_type':self.content_type})'
ERROR
Reverse for 'approve-form' with arguments '()' and keyword arguments '{'content_type': <ContentType: admin sanc model>, 'form_id': 12}' not found. 1 pattern(s) tried: ['flow-control/approve-form/$']
Is something wrong with the approach or is there any better approach for this ?
Thanks in advance.
PS: I tried the url documentation but couldn't figure it out.
Change your url to and check if its worked or not-
urlpatterns = [
# ------------- set relations --------------------
url(r'^approve-form/(?P<content_type>\w+)/(?P<form_id>\d+)/$', views.approve_form, name='approve-form'),]
views
def approve_form(request, content_type=None, form_id=None):
return HttpResponse('Working')

Categories

Resources