Redirects clashing with URL patterns Django - python

I'm trying to get Django's redirects app working, it works with some of the redirects I have in the database, but not all of them.
The ones that match with one of my url patterns 404 without redirecting, the rest are fine.
urlpatterns = [
...
url(r'^(?P<category>[-\w]+)/$', views.SubCategoryListView.as_view(),
name='category_list'),
url(r'^(?P<category>[-\w]+)/(?P<slug>[-\w]+)/$',
views.content_or_sub_category, name='choice')
...
]
For example, say the URL 'example.com/foo/bar' is supposed to redirect. It would match the second url pattern above, get sent to content_or_sub_category, which checks whether 'foo' is a valid category or not, and if 'bar' is a valid slug. Upon finding that they're not, it 404s (as in the code below) and doesn't redirect.
In the docs it says
Each time any Django application raises a 404 error, this middleware checks the redirects database for the requested URL as a last resort.
https://docs.djangoproject.com/en/1.8/ref/contrib/redirects/
But instead of redirects kicking in any time a 404 is raised it appears to only happen if Django doesn't find a matching pattern.
Is that how it's supposed to be behaving?
I found this question Raise 404 and continue the URL chain about how to resume evaluating the rest of the url patterns if one 404s, but it looks like that's either something you can't or shouldn't do.
The only answer on that question suggested putting some logic in your urls.py, but that would need to check the validity of the URL, and then make the urlpatterns list according to whether it's valid or not. I googled and couldn't find a way to do that from within urls.py.
I'm using Django 1.8.
class SubCategoryListView(ListView):
model = Content
queryset = Content.objects.published()
paginate_by = 15
def get_queryset(self):
qs = super(SubCategoryListView, self).get_queryset()
if 'category' in self.kwargs:
if self.kwargs['category'] is not None:
qs = qs.filter(
categories__slug__contains=self.kwargs['category'])
return qs
def get_context_data(self, **kwargs):
context = super(SubCategoryListView, self).get_context_data(**kwargs)
if 'category' in self.kwargs and self.kwargs['category'] is not None:
context['category'] = get_object_or_404(Category,
slug=self.kwargs[
'category'])
return context
...
def content_or_sub_category(self, **kwargs):
sub_category = get_object_or_false(Category.objects.sub_categories(),
slug=kwargs['slug'])
content = get_object_or_false(Content.objects.published(),
slug=kwargs['slug'])
if content:
return ContentDetailView.as_view()(self, **kwargs)
if sub_category:
return ContentListView.as_view()(self,
**{'category': kwargs['category'],
'sub_category': kwargs['slug']})
raise Http404
Some redirects that don't work:
/46421 --> /economy/has-prime-minister-broken-promise-tax-credits/
/are-living-standards-on-the-rise --> /economy/are-living-standards-rise/
/articles/nhs_budget_cut-28646 --> /economy/has-nhs-budget-been-cut/
But something like this does work:
/health/live/2015/jan/number_12_hour_wait_hospital_bed_accident_emergency-38409 --> /health/spike-numbers-waiting-12-hours-e-hospital-bed/

Not sure what return ContentDetailView.as_view()(self, **kwargs) is, but in general if you want to redirect, you would use something along the lines of:
return redirect(reverse('category_list', args=[ARG1, ARG2]))
Where "category_list" is the name of the url pattern, and ARG1 & ARG2 are the arguments you want in the url.

Related

django object has no id, or is at least not passing any id [duplicate]

In my view function I want to call another view and pass data to it :
return redirect('some-view-name', backend, form.cleaned_data)
, where backend is of registration.backends object, and form.cleaned_data is a dict of form data (but both must be either sent as *args or **kwargs to prevent raising Don't mix *args and **kwargs in call to reverse()! error). From what I've found in the docs :
def my_view(request):
...
return redirect('some-view-name', foo='bar')
It looks like I need to provide 'some-view-name' argument, but is it just the name of the view function, or the name of the url ? So I would like to make it similar to the way it's done in django-registration, where :
to, args, kwargs = backend.post_registration_redirect(request, new_user)
return redirect(to, *args, **kwargs)
def post_registration_redirect(self, request, user):
return ('registration_complete', (), {})
Ok so now, can I call directly my view function or do I need to provide a url for it ? And what more important, how my funciotn call (and a url if needed) should look like ? Both backend, and cleaned_data are just passed through this view for a later usage. I've tried this, but it's improper :
url(r'^link/$', some-view-name)
def some-view-name(request, *args):
As well as this :
return redirect('some_url', backend=backend, dataform.cleaned_data)
url(r'^link/$', some-view-name)
def some-view-name(request, backend, data):
still NoReverseMatch . But in django-registration, I've seen something like this :
url(r'^register/$',register,{'backend': 'registration.backends.default.DefaultBackend'}, name='registration_register'),
def register(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='user/login_logout_register/registration_form.html',
extra_context=None):
urls.py:
#...
url(r'element/update/(?P<pk>\d+)/$', 'element.views.element_update', name='element_update'),
views.py:
from django.shortcuts import redirect
from .models import Element
def element_info(request):
# ...
element = Element.object.get(pk=1)
return redirect('element_update', pk=element.id)
def element_update(request, pk)
# ...
Firstly, your URL definition does not accept any parameters at all. If you want parameters to be passed from the URL into the view, you need to define them in the urlconf.
Secondly, it's not at all clear what you are expecting to happen to the cleaned_data dictionary. Don't forget you can't redirect to a POST - this is a limitation of HTTP, not Django - so your cleaned_data either needs to be a URL parameter (horrible) or, slightly better, a series of GET parameters - so the URL would be in the form:
/link/mybackend/?field1=value1&field2=value2&field3=value3
and so on. In this case, field1, field2 and field3 are not included in the URLconf definition - they are available in the view via request.GET.
So your urlconf would be:
url(r'^link/(?P<backend>\w+?)/$', my_function)
and the view would look like:
def my_function(request, backend):
data = request.GET
and the reverse would be (after importing urllib):
return "%s?%s" % (redirect('my_function', args=(backend,)),
urllib.urlencode(form.cleaned_data))
Edited after comment
The whole point of using redirect and reverse, as you have been doing, is that you go to the URL - it returns an Http code that causes the browser to redirect to the new URL, and call that.
If you simply want to call the view from within your code, just do it directly - no need to use reverse at all.
That said, if all you want to do is store the data, then just put it in the session:
request.session['temp_data'] = form.cleaned_data
I do like this in django3
redirect_url = reverse('my_function', args=(backend,))
parameters = urlencode(form.cleaned_data)
return redirect(f'{redirect_url}?{parameters}')
I am new to Django. One of my project, I used render instead of redirect to send data. That worked good. My code was like this --->
for key, value in request.POST.lists():
print(key, value)
if key.split('-')[-1] != 'csrfmiddlewaretoken':
qu_id = key.split('-')[-1]
get_answer = Answer.objects.filter(question_id=qu_id,
correct_answer__option__contains=value[0])
total_correct_answer = get_answer.count()
context = {'score': total_correct_answer}
return render(request, 'result.html', context)
context = {'questions': questions, 'total_correct_answer': total_correct_answer}
return render(request, 'test.html', context)

Django Error NoReverseMatch because it's missing a pk

So currently I'm on a page with a url link like this
urls.py :
path('key/<int:pk>', views.KeyDetailView.as_view(), name='roomkey-detail'),
views.py :
class KeyDetailView(generic.DetailView):
model = RoomKey
this lists out a list of keys available to be borrowed for a particular room. Then when I try to head to the next page, where is a request I can make to borrow out one of the keys for that room, Here is the urls and views responsible for rendering the roomkey-request page
urls.py :
path('key/<int:pk>/request', views.KeyRequestCreate.as_view(), name='roomkey-request')
views.py :
class KeyRequestCreate(CreateView):
model = KeyRequest
fields = ['roomkey', 'requester', 'borrower', 'request_comments']
template_name = 'catalog/roomkey_request_form.html'
there is a button that on that page that links to a terms and agreement page that looks like this
roomkey_request_form.html :
terms and conditions
urls.py :
path('key/<int:pk>/request/agreement', views.KeyAgreement, name='key-agreement'),
views.py :
def KeyAgreement(request):
return render(
request,
'catalog/roomkey_agreement.html',
)
however when try to click on that request button to request a key, django throws an error
NoReverseMatch at /catalog/key/2/request
Reverse for 'key-agreement' with arguments '('',)' not found. 1 pattern(s)
tried: ['catalog\\/key\\/(?P<pk>[0-9]+)\\/request\\/agreement$']
I have a button on the terms and agreement to go back to the request page something that looks like this
<button href="{% url 'roomkey-request' roomkey.pk %}" >Return to request</button>
will this return to the request page with the correct pk? I think i'm just confused with how the url handles pk and how it get's passed on.I am thinking this had to do with something the keyagreement not being able to take in that pk from the details page, can someone please explain to me what I am doing wrong or point me to some resource that can help me understand how urls pass along the pk from view to view? I am fairly new to django so thanks for your help in advance!!
Try:
def KeyAgreement(request, pk): #-->pk in argument
return render(
request,
'catalog/roomkey_agreement.html',
)
If you want to use roomkey.pk in the roomkey_request_form.html template, you need to add roomkey to the template context. You can do this in the get_context_data method.
Since you already have the roomkey pk from the URL, you can remove it from fields. Then set roomkey in the form_valid method before saving.
class KeyRequestCreate(CreateView):
model = KeyRequest
fields = ['requester', 'borrower', 'request_comments']
template_name = 'catalog/roomkey_request_form.html'
def get_context_data(self, **kwargs):
context = super(KeyRequestCreate, self).get_context_data(**kwargs)
context['roomkey'] = get_object_or_404(RoomKey, pk=pk)
def form_valid(self, form):
form.instance.roomkey = get_object_or_404(RoomKey, pk=pk)
return super(KeyRequestCreate, self).get_form(form)
If you want to use roomkey in the agreement view, you'll have to make some changes to it as well.
First, you need to add pk to the function signature since you have <int:pk> in its URL pattern.
Then, you need to include roomkey in the template context.
from django.shortcuts import get_object_or_404
def key_agreement(request, pk):
roomkey = get_object_or_404(roomkey, pk=pk)
return render(
request,
'catalog/roomkey_agreement.html',
{'roomkey': roomkey}
)
Note that I've renamed the view function to key_agreement to match the recommended style. You'll need to update the URL pattern as well.
path('key/<int:pk>/request/agreement', views.KeyAgreement, name='key-agreement'),

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.

Change django haystack search view based on user profile element

So, I've read around here and elsewhere, and I've seen people load different html templates, or do something in the template itself to change what is displayed, but what I am wanting to do is load a different view or form based on an element in the user profile.
My urls.py looks something like this:
urlpatterns = patterns('',
url(r'^$', MySearchView(
searchqueryset=sqs,
form_class=BasicPropertySearchForm
), name="haystack_search"),
)
I can set the line
form_class=
to either BasicPropertySearchForm or PaidPropertySearchForm manually, and the corresponding search page will load properly and function, no problem.
But what I'd like to do is have the form_class automated so it loads BasicPropertySearchForm or PaidPropertySearchForm based on the user profile.
I tried accomplishing this with request.user.get_profile() in the view like this:
class MySearchView(SearchView):
#extends the default haystack SearchView
def which_view(self):
if self.request.user.get_profile().is_paid:
return PaidPropertySearchForm
else:
return BasicPropertySearchForm
and changing
form_class=MySearchView().which_view
but that results in the error
which_view() takes exactly 1 argument (4 given)
I have tried many other variations including using a method that's not within a class in the views.py, doing the if statment in the urls.py (which i realized wouldn't work since I'm imagining I have to access the request.user.get_profile() from withing a view)...
I also tried doing this:
urls.py:
urlpatterns = patterns('',
url(r'^$', 'property_listings.views.search_views', name="haystack_search"),
)
and views.py:
def search_views(request, *args, **kwargs):
sqs = SearchQuerySet().all
if request.user.get_profile().is_paid:
return SearchView(
searchqueryset=sqs,
form_class=PaidPropertySearchForm
)(request, *args, **kwargs)
else:
return SearchView(
searchqueryset=sqs,
form_class=BasicPropertySearchForm
)(request, *args, **kwargs)
but then my forms gave me all sorts of errors about stuff not being defined, like self.searchqueryset.all() all does not exist for that or something or another..
Any help would be appreciated, I must've read 50 different blogs, doc pages, stack overflow posts, etc. over the last few days and I'm banging my head against a wall here..

Django return redirect() with parameters

In my view function I want to call another view and pass data to it :
return redirect('some-view-name', backend, form.cleaned_data)
, where backend is of registration.backends object, and form.cleaned_data is a dict of form data (but both must be either sent as *args or **kwargs to prevent raising Don't mix *args and **kwargs in call to reverse()! error). From what I've found in the docs :
def my_view(request):
...
return redirect('some-view-name', foo='bar')
It looks like I need to provide 'some-view-name' argument, but is it just the name of the view function, or the name of the url ? So I would like to make it similar to the way it's done in django-registration, where :
to, args, kwargs = backend.post_registration_redirect(request, new_user)
return redirect(to, *args, **kwargs)
def post_registration_redirect(self, request, user):
return ('registration_complete', (), {})
Ok so now, can I call directly my view function or do I need to provide a url for it ? And what more important, how my funciotn call (and a url if needed) should look like ? Both backend, and cleaned_data are just passed through this view for a later usage. I've tried this, but it's improper :
url(r'^link/$', some-view-name)
def some-view-name(request, *args):
As well as this :
return redirect('some_url', backend=backend, dataform.cleaned_data)
url(r'^link/$', some-view-name)
def some-view-name(request, backend, data):
still NoReverseMatch . But in django-registration, I've seen something like this :
url(r'^register/$',register,{'backend': 'registration.backends.default.DefaultBackend'}, name='registration_register'),
def register(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='user/login_logout_register/registration_form.html',
extra_context=None):
urls.py:
#...
url(r'element/update/(?P<pk>\d+)/$', 'element.views.element_update', name='element_update'),
views.py:
from django.shortcuts import redirect
from .models import Element
def element_info(request):
# ...
element = Element.object.get(pk=1)
return redirect('element_update', pk=element.id)
def element_update(request, pk)
# ...
Firstly, your URL definition does not accept any parameters at all. If you want parameters to be passed from the URL into the view, you need to define them in the urlconf.
Secondly, it's not at all clear what you are expecting to happen to the cleaned_data dictionary. Don't forget you can't redirect to a POST - this is a limitation of HTTP, not Django - so your cleaned_data either needs to be a URL parameter (horrible) or, slightly better, a series of GET parameters - so the URL would be in the form:
/link/mybackend/?field1=value1&field2=value2&field3=value3
and so on. In this case, field1, field2 and field3 are not included in the URLconf definition - they are available in the view via request.GET.
So your urlconf would be:
url(r'^link/(?P<backend>\w+?)/$', my_function)
and the view would look like:
def my_function(request, backend):
data = request.GET
and the reverse would be (after importing urllib):
return "%s?%s" % (redirect('my_function', args=(backend,)),
urllib.urlencode(form.cleaned_data))
Edited after comment
The whole point of using redirect and reverse, as you have been doing, is that you go to the URL - it returns an Http code that causes the browser to redirect to the new URL, and call that.
If you simply want to call the view from within your code, just do it directly - no need to use reverse at all.
That said, if all you want to do is store the data, then just put it in the session:
request.session['temp_data'] = form.cleaned_data
I do like this in django3
redirect_url = reverse('my_function', args=(backend,))
parameters = urlencode(form.cleaned_data)
return redirect(f'{redirect_url}?{parameters}')
I am new to Django. One of my project, I used render instead of redirect to send data. That worked good. My code was like this --->
for key, value in request.POST.lists():
print(key, value)
if key.split('-')[-1] != 'csrfmiddlewaretoken':
qu_id = key.split('-')[-1]
get_answer = Answer.objects.filter(question_id=qu_id,
correct_answer__option__contains=value[0])
total_correct_answer = get_answer.count()
context = {'score': total_correct_answer}
return render(request, 'result.html', context)
context = {'questions': questions, 'total_correct_answer': total_correct_answer}
return render(request, 'test.html', context)

Categories

Resources