Django: redirecting a view and using include() - python

I'm essentially trying to make my URLs more readable by incorporating a slug that is not used for lookup purposes in my views. Previously, I had this in my main URLconf:
urls.py
path('<int:team_pk>/', TeamDetails.as_view(), name='team_details'),
path('<int:team_pk>/', include('tracker.urls', namespace='tracker')),
So I had urls like mysite/1/stuff and mysite/1/more/stuff.
Then I added a slug field to my Team model. The behavior I want is for any url that only includes a team_pk to be redirected to an expanded URL that incorporates the slug. For example, mysite/1/ should redirect to mysite/1/team_1_slug and mysite/1/stuff should redirect to mysite/1/team_1_slug/stuff. It's this latter behavior that I'm having trouble with.
I modified my URLconf to this:
path('<int:team_pk>/', TeamRedirectView.as_view(), name='team_details'),
path('<int:team_pk>/<slug:team_slug>/', TeamDetails.as_view(), name='team_details_redirected'),
path('<int:team_pk>/<slug:team_slug>/', include('tracker.urls', namespace='tracker')),
Then I have a simple view to redirect:
# views.py
class TeamRedirectView(generic.RedirectView):
def get_redirect_url(self, *args, **kwargs):
team = get_object_or_404(models.Team, pk=self.kwargs['team_pk'])
return reverse('team_details_redirected', kwargs={'team_pk': team.pk, 'team_slug': team.slug})
This works for redirecting mysite/1/ to mysite/1/team_1_slug, but any url tag that points to a url inside the tracker app throws a reverse error because I'm not supplying team_slug to the url tag.
So I'm basically trying to get my RedirectView to be a little more universal and add this slug to any url without having to manually supply it to every url tag. Is that doable?

Related

How to set up URL aliases for dynamically created URLs in DJANGO?

In my site, I have pages that are created on the fly using primary keys (for privacy/security reasons using uuid.uuid4()) as the URLs. I end up with .../reports/e657334b-75e2-48ce-8571-211251f1b341/ Is there a way to make aliases for all of these dynamically created sites to something like .../reports/report/.
Right now my urls.py includes the following:
path("reports/<str:pk>/", views.report_detail, name="report")
I tried changing it to:
re_path('reports/<str:pk>/', RedirectView.as_view(url='/reports/report/'), name="report"),
path("reports/report/", views.report_detail),
But I go to the site that has the links to the long URLs, I get the following error:
NoReverseMatch at /reports/Reverse for 'report' with arguments '('e657334b-75e2-48ce-8571-211251f1b341',)' not found. 1 pattern(s) tried: ['reports/str:pk/']
The template for that site includes:
<a class="card-title" href="{% url 'report' report.pk%}">
I also tried the following for urls:
path("reports/report/", views.report_detail),
path('reports/<str:pk>/', RedirectView.as_view(url='reports/report/'), name="report"),
Which allowed the previous site to load, but when I clicked on the link got the following 404 error:
Request URL: http://127.0.0.1:8000/reports/e657334b-75e2-48ce-8571-211251f1b341/reports/report/
I am trying to have one alias for multiple pages - essentially removing/replacing the long uuid with a single word.
Without trying to make an alias, the site works fine.
If you really don't want to pass the pk/uuid of the report to the shortened url, you could pass it in the session
Create a custom RedirectView that saves the pk to the session and then read that pk in the target view
class ReportRedirect(RedirectView):
def get(self, request, pk):
request.session['report_pk'] = pk
return super().get(request, pk)
def report_detail(request):
report_pk = request.session['report_pk']
...
You use the custom RedirectView just like the built-in one
path("reports/report/", views.report_detail),
path('reports/<str:pk>/', views.ReportRedirect.as_view(url='/reports/report/'), name="report"),

How do you give a wagtail/django Page a custom url to serve at?

In wagtail/django how do you make a basic wagtail Page model, create the html template, and then tell that model to serve as a view for a specific url?
from django.db import models
from wagtail.wagtailcore.models import Page
class MyPage(Page):
title = models.CharField(blank=True, null=True, max_length=255)
#...
I want the url to register like
url(r'^monkey/(?P<slug>[A-Za-z0-9]+)$', ...)
But I don't have a common urls.py folder its stored outside of the project. I tried using the RoutablePageMixin, but I believe that serves for subpages. I also know where to store the html template within the structure so that is not a problem.
You might want something like:
from django.http import Http404
from django.views import View
from wagtail.core.views import serve
class WagtailPageServeView(View):
"""
A default way to serve up wagtail pages in urls
"""
wagtail_slug = None
def get(self, request, *args, **kwargs):
if self.wagtail_slug:
return serve(request, self.wagtail_slug)
else:
raise Http404(
f"WagtailPageServeView missing 'wagtail_slug'. Cannot serve "
f"request.path: {request.path}"
)
Then you can do something like this in urls.py:
urlpatterns += [
re_path(r'^$', WagtailPageServeView.as_view(wagtail_slug='/'), name='home'),
]
In Wagtail, you are creating a page type and then creating instances of the page type in the CMS.
Even if you are only going to use this page once, you still need to make a type. Following this logic, page urls are defined inside the CMS through the page's parent and slug. Most likely, you could achieve what you want through these means.
If you want to override your page template and create a custom url, you can override the get_url_parts method on your model.
Also, if you want to skip Wagtail for this page specifically, remember that Wagtail is just built over Django. You can always create a new module and define the url in that model. If you check your site urls.py, you will see the wagtail url patterns. You can change it to look something like this:
urlpatterns = [
# other urls...
url(r'wagtail/', include(wagtail_urls)),
url(r'^monkey/', ('monkey_app.urls', 'monkey_app')),
]

How can I redirect in django middleware? global name 'view' is not defined

Django newbie here, need help on basic middleware to redirect to another view if a certain model field is empty.
I am creating a terms of agreement page that users must get redirected to right after they signup to the platform if their filed_terms field on their Profile model is empty.
I am using middleware for this. However I am unable to get this to work. This is my middleware class:
class TermsMiddleware(object):
def process_request(self, request):
if request.user.profile.filled_terms is None:
return redirect(reverse(terms))
This gives me the following error:
global name 'terms' is not defined
I also have the url matcher that works perfectly when I navigate to it manually:
url(r'^terms/', 'my_app.views.terms')
I have a terms.html template and a terms view in my views.py file that is working perfectly in all other respects. I have also added it to the settings middleware requirements to make sure it loads.
Do I have to import something from views or url dispatcher into my middleware file? If so what would that be? I have been at this for a while an cannot find anything helpful.
reverse function takes url name instead on the regex. So you need to add name on your url configuration. Here is the example.
url(r'^terms/', 'my_app.views.terms', name='terms')
Add this in your views.py
from django.core.urlresolvers import reverse
And you need to fix your reverse function into.
return redirect(reverse('terms'))
Python interpret your terms as a variable and you have no variable named terms while you need to put string on reverse.

How to use current logged in user as PK for Django DetailView?

When defining URL patterns, I am supposed to use a regular expression to acquire a PK from the URL.
What if I want a URL that has no PK, and if it's not provided, it will use the currently logged in user? Examples:
visiting /user will get a DetailView of the currently logged in user
/user/edit will show an UpdateView for the currently logged in user
I tried hard-coding the pk= in the Detail.as_view() call but it reports invalid keyword.
How do I specify that in the URL conf?
My sample code that shows PK required error when visiting /user URL:
urlpatterns = patterns('',
url(r'user/$',
DetailView.as_view(
model=Account,
template_name='user/detail.html')),
)`
An alternative approach would be overriding the get_object method of the DetailView subclass, something along the line of:
class CurrentUserDetailView(UserDetailView):
def get_object(self):
return self.request.user
Much cleaner, simpler and more in the spirit of the class-based views than the mixin approach.
EDIT: To clarify, I believe that two different URL patterns (i.e. one with a pk and the other without) should be defined separately in the urlconf. Therefore they could be served by two different views as well, especially as this makes the code cleaner. In this case the urlconf might look something like:
urlpatterns = patterns('',
url(r"^users/(?P<pk>\d+)/$", UserDetailView.as_view(), name="user_detail"),
url(r"^users/current/$", CurrentUserDetailView.as_view(), name="current_user_detail"),
url(r"^users/$", UserListView.as_view(), name="user_list"),
)
And I've updated my example above to note that it inherits the UserDetailView, which makes it even cleaner, and makes it clear what it really is: a special case of the parent view.
As far as I know, you can't define that on the URL definition, since you don't have access to that information.
However, what you can do is create your own mixin and use it to build views that behave like you want.
Your mixin would look something like this:
class CurrentUserMixin(object):
model = Account
def get_object(self, *args, **kwargs):
try:
obj = super(CurrentUserMixin, self).get_object(*args, **kwargs)
except AttributeError:
# SingleObjectMixin throws an AttributeError when no pk or slug
# is present on the url. In those cases, we use the current user
obj = self.request.user.account
return obj
and then, make your custom views:
class UserDetailView(CurrentUserMixin, DetailView):
pass
class UserUpdateView(CurrentUserMixin, UpdateView):
pass
Generic views uses always RequestContext. And this paragraph in the Django Documentation says that when using RequestContext with auth app, the template gets passed an user variable that represents current user logged in. So, go ahead, and feel free to reference user in your templates.
You can get the details of the current user from the request object. If you'd like to see a different user's details, you can pass the url as parameter. The url would be encoded like:
url(r'user/(?P<user_id>.*)$', 'views.user_details', name='user-details'),
views.user_details 2nd parameter would be user_id which is a string (you can change the regex in the url to restrict integer values, but the parameter would still of type string). Here's a list of other examples for url patterns from the Django documentation.

confusion in url and template in django

My url is :
1. http://localhost:8000/docs/[slug]
2. http://localhost:8000/docs/[slug]/login
1. url calls before number 2. url I want to send
the slug value to the function mapped by the url 2. In template
what should i wrote for form action event.
I agree, this is nearly incomprehensible, but I'm going to give it a go in terms of an answer.
In terms of calling sequence, there is none. A user might first visit url 2 or url 1. You have no way of guaranteeing which they will try to access first because they might directly input the url into their browser. The only thing you can do is set a variable in request.session's dict and test for it with your login url.
In terms of passing slug to another url, if you're having a url with this in it:
urls = ('',
url(r'docs/(?P<slug>\w+)', 'app.views.slug', name='slug-view'),
url(r'docs/(?P<slug>\w+)/login', 'app.views.slug_login', name='slug-login'),
#..
)
Then in your template you can do this:
<form action="{% url slug-login slugname %}" method="POST">
Your views.py would look something like this.
def slug(request, slug):
#
#
return render_to_response('templatename.html', {'slugname':slug}, context_instance=RequestContext(request))
def slug_login(request, slug):
# do something with slug.
This way, when you access the slug view, you pass into the template a variable called slugname, which the template uses with django's url library to resolve a specifically named url in urls.py with one named parameter, slug, which it will assign the value of slugname.
I suggest you try it.
I might also reccoment you read up on the django url dispatcher. Your use of regex without named parameters is acceptable but really not best practice. I'd also suggest django shortcuts (render_to_response) as a quick way to pass variables into templates and the django template language itself.

Categories

Resources