Django: Extend context of class based view with Admin context - python

I have a class based view which just displays a list of configurations.
This view is added to the Django Admin site by using the following code:
#admin.register(ZbxHostConf)
class ZbxHostConfListViewAdmin(admin.ModelAdmin):
review_template = 'admin/admzbxhostconf_list.html'
def get_urls(self):
urls = super(ZbxHostConfListViewAdmin, self).get_urls()
my_urls = patterns('',
(r'^zbxhostconflist/$', self.admin_site.admin_view(self.review)),
)
return my_urls + urls
def review(self, request):
return ZbxHostConfListView.as_view()(request)
The template extends the admin/base_site.html template. I can access the site only after I log in to the Django Admin site. Unfortunately the template cannot access the context data provided by the admin view.
As the Django documentation suggests the context data will be provided directly to the TemplateResponse function:
def my_view(self, request):
# ...
context = dict(
# Include common variables for rendering the admin template.
self.admin_site.each_context(request),
# Anything else you want in the context...
key=value,
)
return TemplateResponse(request, "sometemplate.html", context)
For function-based views there is the possibility of the extra_context argument but class-based views don't provide this argument. I guess I have to modify the get_context_data function but I don't really understand how I can provide the admin context data to the get_context_data function of my class based view. Any suggestions?

This might not be a correct answer, but I believed you could try something like this.
#!/usr/bin/python3
from django.contrib import admin
class MyTemplateView(TemplateView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(admin.site.each_context(self.request))
return context

Related

Django admin add context to index.html

I'm trying to override Django admin index.html to show table contain latest 5 records from a model
I managed to override the template with the following in my app:
from django.contrib import admin
admin.site.index_template = 'admin/dashboard.html'
admin.autodiscover()
But i have no idea how to add the latest records from model as a context to the index.html
Hope someone can help me
class CustomAdminSite(admin.AdminSite):
def index(self, request, extra_context=None):
extra_context = extra_context or {}
# Add your context here
return super(CustomAdminSite, self).index(request, extra_context)
admin_site = CustomAdminSite()
Don't forget to replace default admin with admin_site
Something that worked for me to add extra context was using context processor. Explained here: https://stackoverflow.com/a/34903331/13436391
Django documentation

Django - logout view customisation fail

Although the original question is cleared out, there's a new related question which is quite interesting: (please let me know if my theory is remotely correct...)
Please checkout my urlpatterns below:
from django.contrib.auth import views as auth_views
from boutique.models import Category
from django.urls import path, include
from . import views
app_name = 'users'
urlpatterns = [
# to customise login view
path('login/', auth_views.LoginView.as_view(extra_context = {'categories': Category.objects.get_categories_with_item()})),
# path('login/', views.NewLoginView.as_view()),
# to customise default logout view
path('logout/', auth_views.LogoutView.as_view(), {'categories': Category.objects.get_categories_with_item()}),
# include django authentication urls
path('', include('django.contrib.auth.urls')),
As you probably noticed that there are two different ways to pass in extra_context, the interesting thing is: The method used on LogoutView can not be used on LoginView!! However, The method used on LoginView does work on LogoutView.
I think the possible explanation and yet to be confirmed by you lot is that they inherit different views:
LoginView:
class LoginView(SuccessURLAllowedHostsMixin, FormView):
"""
Display the login form and handle the login action.
"""
form_class = AuthenticationForm
authentication_form = None
redirect_field_name = REDIRECT_FIELD_NAME
template_name = 'registration/login.html'
redirect_authenticated_user = False
extra_context = None
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
current_site = get_current_site(self.request)
context.update({
self.redirect_field_name: self.get_redirect_url(),
'site': current_site,
'site_name': current_site.name,
**(self.extra_context or {})
})
return context
LogoutView:
class LogoutView(SuccessURLAllowedHostsMixin, TemplateView):
"""
Log out the user and display the 'You are logged out' message.
"""
next_page = None
redirect_field_name = REDIRECT_FIELD_NAME
template_name = 'registration/logged_out.html'
extra_context = None
...
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
current_site = get_current_site(self.request)
context.update({
'site': current_site,
'site_name': current_site.name,
'title': _('Logged out'),
**(self.extra_context or {})
})
return context
I think the reason that LoginView has to pass the extra_context as a positional argument into .as_view() is that it doesn't inherit from TemplateView:
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
Render a template. Pass keyword arguments from the URLconf to the context.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
which has a get method to get context data... if i'm not mistaken.
Could anyone confirm this?
I'm quite new to python and django framework. Thanks a lot!!!
The Original question has been answered
Please checkout the answers in the comments session --
I'm fairly new to Django and in order to pass in more context to logout view I tried 2 methods to customise the logout view:
Method 1:
from django.contrib.auth import views as auth_views
from boutique.models import Category
app_name = 'users'
urlpatterns = [
...
path('logout/', auth_views.LogoutView.as_view(), {'categories': Category.objects.all()}),
# I also tried this:
# path('logout/', auth_views.LogoutView.as_view({'categories': Category.objects.all()}),
# I also tried this:
# path('logout-customised-url/', auth_views.LogoutView.as_view(), {'categories': Category.objects.all()}),
# This is working tho it wouldn't be automatically redirect to this path when logged out
Method 2:
...
path('logout/', views.NewLogoutView.as_view()),
# NewLogoutView code below:
views.py
from boutique.models import Category
from django.contrib.auth.views import LogoutView
class NewLogoutView(LogoutView):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
Still not working, and the outcome is exactly the same: if a customised url is in use such as 'logged-out/' instead of 'logout/', and you type out the url, it renders the correct page with the extra context. However, wouldn't go to the page when users log out normally...
Is there any workaround? Thanks!
You need to pass extra_context in your urls.py inorder to send extra context to logout view. read more about passing extra context to a view from in url definition.
path(
'logout/',
auth_views.LogoutView.as_view(),
{'extra_context':{'categories': Category.objects.all()}}
),
Got it!! The problem was in the order of my urlpatterns, originally it was like this:
urlpatterns = [
# include django authentication urls
path('', include('django.contrib.auth.urls')),
# to customise default logout view
path('logout/', auth_views.LogoutView.as_view(), {'categories': Category.objects.get_categories_with_item()}),
Based on official doc: Authentication Views, django.contrib.auth.urls includes many url patterns, such as:
accounts/login/ [name='login']
accounts/logout/ [name='logout']
accounts/password_change/ [name='password_change']
accounts/password_change/done/ [name='password_change_done']
accounts/password_reset/ [name='password_reset']
accounts/password_reset/done/ [name='password_reset_done']
accounts/reset/<uidb64>/<token>/ [name='password_reset_confirm']
accounts/reset/done/ [name='password_reset_complete']
Therefore, they take precedence if they were declared before the customised. So:
urlpatterns = [
# to customise default logout view
path('logout/', auth_views.LogoutView.as_view(), {'categories': Category.objects.get_categories_with_item()}),
# include django authentication urls
path('', include('django.contrib.auth.urls')),
To swap the order, it works.
To the roockies like me:
Method 1 does work. Just pass in the additional context/argument like it states in the official doc after the view.
Also, I checked django.contrib.auth.views.LogoutView:
class LogoutView(SuccessURLAllowedHostsMixin, TemplateView):
"""
Log out the user and display the 'You are logged out' message.
"""
next_page = None
redirect_field_name = REDIRECT_FIELD_NAME
template_name = 'registration/logged_out.html'
extra_context = None
...
The extra_context is set to None for you to add extra context. Easy to customise if were to subclass it or simply pass in a positional argument into .as_view() -- which means:
urlpatterns = [
# to customise login view
path('login/', auth_views.LoginView.as_view(extra_context = {'categories': Category.objects.get_categories_with_item()})),
]
`Loginview` has the same the same convenient settings...

The view blog.views.BlogViews didn't return an HttpResponse object. It returned None instead

I'm having a problem with the views for my blog main page. I've read a lot of problems with the same error but couldn't find a solution that i thought matched with my problem.
I am trying to emulate my old code with a new View so that i can customise it further in the future.
path('', ListView.as_view(
queryset=Post.objects.filter(posted_date__lte=now).order_by("-posted_date")[:25],
template_name="blog/blog.html")),
i was previously using just the above url pattern to display my blog posts but im moving to a view:
def BlogViews(TemplateView):
def get(self, request):
posts = Post.objects.all().order_by("-posted_date")[:25]
args = {
'posts' : posts,
}
return render(request, 'blog/blog.html', args)
and the new url
path('', BlogViews, name='blog'),
However this view is not working and returns the error in the title.
I'm importing TemplateView, Post and render correctly i believe.
TemplateView is a class-based-view, so you subclass it with class.
class BlogViews(TemplateView):
def get(self, request):
posts = Post.objects.all().order_by("-posted_date")[:25]
args = {
'posts' : posts,
}
return render(request, 'blog/blog.html', args)
Then use .as_view() in the URL pattern.
path('', BlogViews.as_view(), name='blog'),
Avoid overriding get or post for class-based views. You end up losing the functionality of the parent class or duplicating it. You can take your ListView.as_view(...) as the starting point for the view, for example:
class BlogView(ListView):
template_name="blog/blog.html"
def get_queryset(self):
# Use get_queryset instead of queryset because you want timezone.now()
# to be called when the view runs, not when the server starts
return Post.objects.filter(posted_date__lte=timezone.now()).order_by("-posted_date")[:25]
In your case, it might be easier to use a function-based-view:
def blog_view(request):
posts = Post.objects.all().order_by("-posted_date")[:25]
args = {
'posts' : posts,
}
return render(request, 'blog/blog.html', args)
Then include it in the URLs with:
path('', blog_view, name='blog'),

Django, redirect all non-authenticated users to landing page

I have a django website with many urls and views. Now I have asked to redirect all non-authenticated users to a certain landing page. So, all views must check if user.is_authenticated() and return to a new set of landing pages.
Can it be done in a pretty way, instead of messing with my views.py/urls.py that much?
There is a simpler way to do this, just add the "login_url" parameter to #login_required and if the user is not login he will be redirected to the login page. You can find it here
from django.contrib.auth.decorators import login_required
#login_required(login_url='/accounts/login/')
def my_view(request):
...
You can use Middleware.
Something like this will check user auth every request:
class AuthRequiredMiddleware(object):
def process_request(self, request):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('landing_page')) # or http response
return None
Docs: process_request
Also, don't forget to enable it in settings.py
MIDDLEWARE_CLASSES = (
...
'path.to.your.AuthRequiredMiddleware',
)
see the docs for login required decorator
from django.contrib.auth.decorators import login_required
#login_required
def my_view(request):
...
another option is to add it to your urls.py patterns, see this answer
urlpatterns = patterns('',
(r'^foo/$', login_required(direct_to_template), {'template': 'foo_index.html'}),
)
As of Django 1.10, the custom middleware classes must implement the new style syntax. You can use the following class to verify that the user is logged in while trying to access any views.
from django.shortcuts import HttpResponseRedirect
class AuthRequiredMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
if not request.user.is_authenticated: # in Django > 3 this is a boolean
return HttpResponseRedirect('login')
# Code to be executed for each request/response after
# the view is called.
return response
You can avoid specifying login_url by setting LOGIN_URL.
Therefore, in settings.py add:
LOGIN_URL = '<some_url>'
And in your views.py annotate relevant functions with only #login_required:
#login_required
def some_view_function(request):
If you need to redirect within a view function, you can do so with:
return redirect_to_login(request.get_full_path())
This can be done with middleware.
I've found a really nifty djangosnippet that does exactly what you are asking for. You can find it here, and it looks like:
from django.http import HttpResponseRedirect
from django.conf import settings
from re import compile
EXEMPT_URLS = [compile(settings.LOGIN_URL.lstrip('/'))]
if hasattr(settings, 'LOGIN_EXEMPT_URLS'):
EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS]
class LoginRequiredMiddleware:
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which
you can copy from your urls.py).
Requires authentication middleware and template context processors to be
loaded. You'll get an error if they aren't.
"""
def process_request(self, request):
assert hasattr(request, 'user'), "The Login Required middleware\
requires authentication middleware to be installed. Edit your\
MIDDLEWARE_CLASSES setting to insert\
'django.contrib.auth.middlware.AuthenticationMiddleware'. If that doesn't\
work, ensure your TEMPLATE_CONTEXT_PROCESSORS setting includes\
'django.core.context_processors.auth'."
if not request.user.is_authenticated():
path = request.path_info.lstrip('/')
if not any(m.match(path) for m in EXEMPT_URLS):
return HttpResponseRedirect(settings.LOGIN_URL)
All you have to do is to save the file as middleware.py and include the class in you're settings.py, i.e.
MIDDLEWARE_CLASSES += ('projectname.common.middleware.RequireLoginMiddleware',)
You can also define a LOGIN_URL in settings.py, so that you'll be redirected to your custom login page. The default LOGIN_URL is '/accounts/login/'.
Maybe too late but in django 1.9+ it's too easy.
Django introduced Login Required mixin for generic classes and this a great example
here by William S. Vincent
simply in your view add LoginRequiredMixin as parent class
from django.contrib.auth.mixins import LoginRequiredMixin
class BlogUpdateView(LoginRequiredMixin, UpdateView):
model = Post
template_name = 'post_edit.html'
fields = ['title', 'body']
Also you can use login_required decorator for method request
from django.contrib.auth.decorators import login_required
#login_required(login_url='/login/')
def home(request):
return render(request, "home.html")
It's showing like this: http://127.0.0.1:1235/login/?next=/home/
I'm using class based views and I couldn't get any of these solutions to work for what I needed so I'll post my working solution here.
class ManagementPageView(TemplateView):
# Make a dispatch method to handle authentication
def dispatch(self, *args, **kwargs):
# Check if user is authenticated
if not self.request.user.is_authenticated:
# Redirect them to the home page if not
return redirect('home')
# Render the template if they are
return render(self.request, 'management.html')

Django Class Views and Reverse Urls

I have a good many class based views that use reverse(name, args) to find urls and pass this to templates. However, the problem is class based views must be instantiated before urlpatterns can be defined. This means the class is instantiated while urlpatterns is empty leading to reverse throwing errors. I've been working around this by passing lambda: reverse(name, args) to my templates but surely there is a better solution.
As a simple example the following fails with exception:
ImproperlyConfigured at xxxx
The included urlconf mysite.urls doesn't have any patterns in it
mysite.urls
from mysite.views import MyClassView
urlpatterns = patterns('',
url(r'^$' MyClassView.as_view(), name='home')
)
views.py
class MyClassView(View):
def get(self, request):
home_url = reverse('home')
return render(request, 'home.html', {'home_url':home_url})
home.html
<p><a href={{ home_url }}>Home</a></p>
I'm currently working around the problem by forcing reverse to run on template rendering by changing views.py to
class MyClassView(View):
def get(self, request):
home_url = lambda: reverse('home')
return render(request, 'home.html', {'home_url':home_url})
and it works, but this is really ugly and surely there is a better way. So is there a way to use reverse in class based views but avoid the cyclic dependency of urlpatterns requiring view requiring reverse requiring urlpatterns...
EDIT:
I'm using this as so:
views.py
def sidebar_activeusers(cls):
sidebar_dict = {'items' = []}
qs = models.random.filter.on.users
for user in qs:
item = {
'value': user.name,
'href': reverse('user_profile', args=[hash_id(user.id)])}
sidebar = loader.get_template('sidebar.html')
cls.base_dict['sidebar_html'] = sidebar.render(Context(sidebar_dict))
return cls
#sidebar_activeusers
class MyView1(View):
base_dict = {}
...
#other_sidebar_that_uses_same_sidebar_template
class MyView2(View):
basically I'd like to use the same sidebar template for a few different content types. Although the models displayed in the sidebar will be arbitrary the format will always be the same.
For your example MyClassView, it's ok to use reverse
class MyClassView(View):
def get(self, request):
home_url = reverse_lazy('home')
return render(request, 'home.html', {'home_url': home_url},
The reverse method is not called when the class is defined -- It is only called when the request is processed and the get method is called, so there shouldn't be an error. I have tested the example above and it works fine.
However, When you use your sidebar_activeusers decorator, the reverse call occurs before the url conf is loaded. In cases like these, you can use reverse_lazy. Here's an example of reverse_lazy in action:
from django.core.urlresolvers import reverse_lazy
class MyOtherClassView(View):
home_url = reverse_lazy('home')
def get(self, request):
return render(request, 'home.html', {'home_url':self.home_url})
This time, home_url is set when the class is defined, so reverse_lazy is required instead of reverse, because the url conf hasn't loaded yet.
Your home url can be accessed directly in your template by using declaring {% url 'home' %}. It's a standard way to access your named urls in your urls.py file.
You do not have to send the home_url variable to your template explicitly in your class-based view function.
In other words, in your home.html file:-
<p>Home</p>
will do.

Categories

Resources