I am using django-livesettings to save the site title to the database. To be able to access a config value, however, you need to pass the variable to the template via a view:
http://django-livesettings.readthedocs.org/en/latest/usage.html#accessing-your-value-in-a-view
What method of the admin.ModelAdmin class can I override to pass variables to the base_site.html, where the admin site title "Django Site Admin" is located?
This answer may come close but I don't know what it misses:
Django how to pass custom variables to context to use in custom admin template?
The most elegant solution I found is here:
https://stackoverflow.com/a/36421610/7521854
Essentially, it overrides your admin site's each_context method to add as many custom variables as required. The updated context is applied to all admin pages without any further effort.
In my case, I want to have a custom footer displaying release version info. This info is taken from a file which is automatically updated with a git describe command during deployment.
File: app-name/sites.py:
class MyAdminSite(AdminSite):
""" Extends the default Admin site. """
site_title = gettext_lazy('My Admin')
site_header = gettext_lazy('My header')
index_title = gettext_lazy('My Administration')
def each_context(self, request):
version_info = ""
try:
version_info = os.environ['RELEASE_TAG']
except KeyError:
f = open(os.path.join(settings.BASE_DIR, 'assets/version.txt'), 'r')
version_info = f.read()
f.close()
os.environ['RELEASE_TAG'] = version_info
context = super(MyAdminSite, self).each_context(request)
context['releaseTag'] = version_info
return context
admin_site = MyAdminSite(name='my_custom_admin')
And the related footer tag is:
{% block footer %}
<div class="copyright-center">
<p><small>My Admin {{releaseTag}} Copyright © MyCo</small></p>
</div>
{% endblock %}
The second link is about adding extra variable to the changelist page of a ModelAdmin.
The base_site.html is extended by many admin pages and thus views, if you want some global change, you need to either extends context of all the relative views or, override the base_site.html itself by setting a variable in the context
Related
My template structure is base.html where i included navbar.html inside the base
I have an app called tags and tags has a models.py and a views.py
inside the views.py, i have a django code as this
from tags.models import Tag
class TagList(ListView):
model = Tag
def get_queryset(self):
return Tags.object.all()
this works, when i call {{ object_list }} inside my template for the tag_list.html.
So i added the {{ object_list }} inside my template navbar.html which was included inside the base.html but it works only when am on the url that displays my tag_list.html and does not show anything when am on other urls or other templates..
How do I make it show irrespective of the template directory am inside or the url am displaying ... i want it to show
I've thought of copying my views.py code into every app view and repeat the same process for every templates but i have a lot of template directories i cannnot do this for all of them
If you need this variable for all pages you can use a context processor.
Otherwise a mixin class similar to ContextMixin could also do the job:
# views.py
class MyContextMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['tags_list'] = Tags.object.all()
return context
class TagList(MyContextMixin, ListView):
model = Tag
You can then extend each View as follows:
# other views.py
from tags.views import MyContextMixin
class OtherView(MyContextMixin, ...):
...
In both cases you add another context variable. Note that I renamed object_list to tags_list to not override the variable name used by ListView.
I have a generic Django view that renders a template. The template is in an app which other projects will use. Importing projects will typically subclass the View the app provides. The View has a default template, which does a job with generic wording.
99% of the time, subclassing Views will want to only change the text, so rather than make them duplicate the template for the sake of altering non-markup wording, i'm looking for a way to allow users of the class to replace wording in the template in the most efficient way.
Options explored so far:
template partials containing only the text which using apps can override (magic, a lot of user work)
A template_strings method on the view which provides a dict of strings which end up in the template context which subclasses can override
Using (abusing?) the translation system such that the app provides default english translations and using code can provide their own translations instead (not actually worked this one out yet, just an idea)
Doing the above template_strings through AppConfig, but this seems ... yucky like it may get very unweildy with a lot of English strings. If doing this I would create a context-like setup so you don't have to re-declare all strings
Seems like it should be a solved problem to subclass a view which does a complete job and just provide alternate strings for text. Is there a better method than the above? Convention? Something I am missing?
(django 1.11 Python 3.6.2)
You can either inherit TemplateView or add ContextMixin to your view, and then override the get_context_data function like this:
from django.views.generic import TemplateView
class BaseView(TemplateView):
template_name = "common.html"
class SubView(BaseView):
def get_context_data(self, **kwargs):
context = super(SubView, self).get_context_data(**kwargs)
context['content'] = "Some sub view text"
return context
Update: Use template overriding
If you want to separate the text out, this is the better way to go
To allow easily and DRY override template across apps, you might need to install this package (Some other detail here)
We define it similarly as above, but change the template_name instead:
from django.views.generic import TemplateView
class BaseView(TemplateView):
template_name = "main.html"
# on another app
class SubView(BaseView):
template_name = "sub_view.html"
Then the magic is you can extends and override block of the BaseView template like this:
base_app/templates/main.html
<p>I'm Common Text</p>
{% block main %}
<p>I'm Base View</p>
{% endblock %}
sub_app/templates/sub_view.html
{% extends "base_app:main.html" %}
{% block main %}
<p>I'm Sub View</p>
{% endblock %}
The result would be:
<p>I'm Common Text</p>
<p>I'm Sub View</p>
Afaik you covered the options pretty well. My example is probably just a variant of the the template strings but maybe it helps anyway...
class DefaultStringProvider():
TITLE = 'Hello'
DESCRIPTION = 'Original description'
CATEGORY = 'Stuff'
class MyTemplateBaseView(TemplateView):
def get_context_data(self, **kwargs):
return super(MyTemplateBaseView, self).get_context_data(
provider=self.get_string_provider(), **kwargs)
def get_string_provider(self):
return DefaultStringProvider()
class OtherView(MyTemplateBaseView):
template_name = 'welcome.html'
def get_string_provider(self):
p = DefaultStringProvider()
p.TITLE = 'Hello'
p.DESCRIPTION = 'New description'
return p
The idea is to have a default string provider and the base view populates the context with it through get_string_provider().
It will at least be quite clear which strings can be overridden for a user extending the base class and it will not interfere with translations.
I am trying to implement sharing in my registration-required website. I would like the user to be able to share a page for a certain duration of time (1 day, 1 week, etc.) so that anyone with a special link can access that page. Is this possible in Django?
EDIT: My solution (based on Saurabh Goyal’s answer):
Add a new model to your models.py, something like this:
class ShareKey(models.Model):
location = models.TextField() # absolute path
token = models.CharField(max_length=40, primary_key=True)
creation_date = models.DateTimeField(auto_now_add=True)
expiration_seconds = models.BigIntegerField()
data = PickledObjectField() # custom sharing data
(The data field is optional, and requires django-picklefield).
In your views.py, add a decorator function like this:
def allow_shares(view_func):
def sharify(request, *args, **kwargs):
shared = kwargs.get('__shared', None)
if shared is not None:
if '__shared' not in view_func.func_code.co_varnames[:view_func.func_code.co_argcount]:
del kwargs["__shared"]
return view_func(request, *args, **kwargs)
else: return login_required(view_func)(request, *args, **kwargs)
return sharify
This decorator allows a view to require a login, unless the page is shared.
Decorate any views you want to be shareable with #allow_shares.
Add a new Exception subclass, SharifyError, like this:
class SharifyError(Exception):pass
Also in views.py, add a view to resolve the shared URLs, like this:
def sharedPage(request, key):
try:
try:
shareKey = ShareKey.objects.get(pk=key)
except: raise SharifyError
if shareKey.expired: raise SharifyError
func, args, kwargs = resolve(shareKey.location)
kwargs["__shared"] = True
return func(request, *args, **kwargs)
except SharifyError:
raise Http404 # or add a more detailed error page. This either means that the key doesn’t exist or is expired.
Add a url to urls.py, like this:
urlpatterns = patterns('',
# ...
url(r'^access/(?P<key>\w+)$', views.sharedPage, name="sharedPage"),
# ...
)
Finally, add URLs to create a shared link, and implement the view like this:
# in imports
from django.utils.crypto import get_random_string
def createShare(request, model_id):
task = MyModel.objects.get(pk=model_id)
key = ShareKey.objects.create(pk=get_random_string(40),
expiration_seconds=60*60*24, # 1 day
location = task.get_absolute_url(),
)
key.save()
return render(request, 'share.html', {"key":key});
(Your share.html template should look something like this):
{% extends 'base.html' %}
{% block content %}
<h1>Sharing link created.</h1>
<p>The link is {{ base_url }}{% url 'taskShared' key.pk %}. It will be valid until {{ key.expiration_date|date:"l, N dS" }} at {{ key.expiration_date|time:"g:i a" }}.</p>
{% endblock %}
This will require users to login to view the decorated pages unless they have entered a key.
Yes, you can do that simply by having one additional field in dB which will represent the last datetime till when page is accessible by all. Then whenever someone accesses it, just check if current datetime is before or equal to value of that field, if yes, let them access, if no, deny access.
When user makes request to make page accessible, create the special link and update the field accordingly.
Make sure to handle routing for special link you generate on every request.
Edit-
To handle things using keys, you can take following steps-
1) generate a random key whenever user requests for special link,
2) store the key and corresponding latest datetime of access for page in db
3) assuming main url for your page was '/mypage/', and your urls.py is something like this-
from django.conf.urls import patterns, url
from apps.myapp import views as myapp_views
# See: https://docs.djangoproject.com/en/dev/topics/http/urls/
urlpatterns = patterns(
'',
url(r'^mypage/$', myapp_views.MyPageView.as_view(), name='mypage'),
)
you add another url, something like this-
from django.conf.urls import patterns, url
from apps.myapp import views as myapp_views
# See: https://docs.djangoproject.com/en/dev/topics/http/urls/
urlpatterns = patterns(
'',
url(r'^mypage/$', myapp_views.MyPageView.as_view(), name='mypage'),
url(r'^mypage/(?P<key>\w+)/$', myapp_views.MyPageView.as_view(), name='mypage_special'),
)
What above results in is that a url '/mypage/any_alphanumeric_key/' will also redirect to your page view.
4) In your views, access the key, fetch the latest datetime of access for that key from dB, and if valid, give access else deny access.
I have two models : Advertisment and Banner
when I using "generic view" How can I Bring together at the same time
The code below bring only one Advertisment
urlpatterns = patterns('',
url(r'^(?P<pk>\d+)/$', DetailView.as_view(
model = Advertisment,
context_object_name = 'advertisment',
), name='cars-advertisment-detail'),
url(r'^$', SearchView.as_view(), name='cars-advertisment-search'),
)
Aidan's answer is good if you only want to do it for a single view, but if you want to show banners on each page automatically, you have two main options.
One is to create a template tag that renders the banner, and add this tag to your templates where you want banners to be shown.
Your tag could look like this:
#register.inclusion_tag('banner.html')
def banner_display():
random_banner = Banner.objects.order_by('?')[0]
return {'the_banner': random_banner}
Then, you would create a template that shows the banner:
<img src="{{ the_banner.url|safe }}" />
In your templates, where you need the banner, you would just say {% banner_display %}
The other option you have is to create a custom template context processor. This will inject your banner as a normal variable in all requests. This is perhaps even simpler:
def banner_display(request):
random_banner = Banner.objects.order_by('?')[0]
return {'the_banner': random_banner}
You should save this in a file and then add it to your TEMPLATE_CONTEXT_PROCESSORS setting. Now in every template you have a variable {{ the_banner }}.
You need to override the get_context_data() method of the class based view (as described in the docs).
from django.views.generic import DetailView
class YourDetailView(DetailView):
model = Advertisment
context_object_name = 'advertisment'
def get_context_data(self, *args, **kwargs):
context = super(YourDetailView, self).get_context_data(*args, **kwargs)
if 'banner_id' in self.kwargs:
context['banner'] = get_object_or_404(Banner, pk=self.kwargs['banner_id']
return context
I guess you'll need to update your url conf to include a primary key for the Banner model too.
from your_app.views import YourDetailView
url(r'^(?P<ad_pk>\d+)/(?P<banner_pk>\d+)/$', YourDetailView.as_view(), name='cars-advertisment-detail'),
I want to create custom page for admin panel without model. For first i copy index.html to project folder:
mysite/
templates/
admin/
index.html
Then add to apps block my code:
<div class="module">
<table summary="{% blocktrans with name="preferences" %}Models available in the preferences application.{% endblocktrans %}">
<caption>{% blocktrans with name="preferences" %}Preferences{% endblocktrans %}</caption>
<tr>
<th scope="row">Preferences</th>
<td>{% trans 'Change' %}</td>
</tr>
</table>
</div>
This works good, then I create new page /templates/admin/preferences/preferences.html and
add to urls.py:
url(r'^admin/preferences/$', TemplateView.as_view(template_name='admin/preferences/preferences.html')),
And add code to preferences.html:
{% extends "admin/base_site.html" %}
{% block title %}Test page{% endblock %}
Run it and see message with error "The requested admin page does not exist.". What I do wrong?
You need to add your admin URL before the URL patterns of the admin itself:
urlpatterns = patterns('',
url(r'^admin/preferences/$', TemplateView.as_view(template_name='admin/preferences/preferences.html')),
url(r'^admin/', include('django.contrib.admin.urls')),
)
This way the URL won't be processed by Django's admin.
Years go by and still a relevant answer to this can be posted.
Using Django 1.10+ you can do:
security/admin.py (this is your app's admin file)
from django.contrib import admin
from django.conf.urls import url
from django.template.response import TemplateResponse
from security.models import Security
#admin.register(Security)
class SecurityAdmin(admin.ModelAdmin):
def get_urls(self):
# get the default urls
urls = super(SecurityAdmin, self).get_urls()
# define security urls
security_urls = [
url(r'^configuration/$', self.admin_site.admin_view(self.security_configuration))
# Add here more urls if you want following same logic
]
# Make sure here you place your added urls first than the admin default urls
return security_urls + urls
# Your view definition fn
def security_configuration(self, request):
context = dict(
self.admin_site.each_context(request), # Include common variables for rendering the admin template.
something="test",
)
return TemplateResponse(request, "configuration.html", context)
security/templates/configuration.html
{% extends "admin/base_site.html" %}
{% block content %}
...
{% endblock %}
See Official ModelAdmin.get_urls description (make sure you select proper Django version, this code is valid for 1.10 above)
Note the use of get_urls() above.
This new admin page will be
accessible under:
https://localhost:8000/admin/security/configuration/
This page will be protected under admin login area
You should be using admin's get_urls.
If you want to create a custom page just to place there an arbitrary form to handle user input, you may give django-etc a try. There's etc.admin.CustomModelPage you can use:
# admin.py
from etc.admin import CustomModelPage
class MyPage(CustomModelPage):
title = 'My custom page' # set page title
# Define some fields you want to proccess data from.
my_field = models.CharField('some title', max_length=10)
def save(self):
# Here implement data handling.
super().save()
# Register the page within Django admin.
MyPage.register()
Here's an example of everything that should be needed (as of Django 1.6) for a custom admin page that is linked to from a button next to the "History" button in the top right of an object's detail page:
https://gist.github.com/mattlong/4b64212e096766e058b7
Full example:
from django.urls import path
from django.contrib import admin
from django.db import models
class DummyModel(models.Model):
class Meta:
verbose_name = 'Link to my shiny custom view'
app_label = 'users' # or another app to put your custom view
#admin.register(DummyModel)
class DummyModelAdmin(admin.ModelAdmin):
def get_urls(self):
view_name = '{}_{}_changelist'.format(
DummyModel._meta.app_label, DummyModel._meta.model_name)
return [
path('my_view/', MyCustomView.as_view(), name=view_name)
]
With this approach Django's makemigrations command will create DB migration to create table for DummyModel.
Extending the AdminSite class worked best for me, as per django's documentation. This solution protects the page(s) under the admin site login mechanism, and setting it up is easier than it may look:
Where you have other admin code (eg. myapp/admin.py), extend the default class:
from django.contrib.admin import AdminSite
class CustomAdminSite(AdminSite):
def get_urls(self):
custom_urls = [
path('admin/preferences/', self.admin_view(views.my_view)),
]
admin_urls = super().get_urls()
return custom_urls + admin_urls # custom urls must be at the beginning
site = CustomAdminSite()
# you can register your models on this site object as usual, if needed
site.register(Model, ModelAdmin)
Implement the view
def my_view(request):
return render(request, 'admin/preferences/preferences.html')
Use that admin site in urls.py, instead of the default one
from myapp import admin
# now use admin.site as you would use the default django one
urlpatterns = [
# ...
path('admin/', admin.site.urls),
# ...
]
If you want to hook a page into the existing admin site, then you can do the following, which is based on #arnaud-p's answer above. Arnaud's answer didn't work for me, as subclassing adminsite's get_url function lost access to existing admin pages until I added the registry as follows.
Using the following method, your additional pages will require staff access, and you don't need to change your urls.py, so this is great for making admin pages for apps etc... You can pass each_context in the view in order to get permissions etc.
works for django 3.2.9
In admin.py
from django.contrib import admin
from django.urls import path
from . import views
class CustomAdminSite(admin.AdminSite):
def get_urls(self):
self._registry = admin.site._registry
admin_urls = super().get_urls()
custom_urls = [
path('preferences/', views.Preferences.as_view(admin=self), name="preferences"),
]
return custom_urls + admin_urls # custom urls must be at the beginning
def get(self):
request.current_app == self.name
return super().get(request)
def get_app_list(self, request):
app_list = super().get_app_list(request)
app_list += [
{
"name": "My Custom Preferences App",
"app_label": "Preferences",
# "app_url": "/admin/test_view",
"models": [
{
"name": "Preferences",
"object_name": "preferences",
"admin_url": "/admin/preferences",
"view_only": True,
}
],
}
]
return app_list
site = CustomAdminSite()
the view...
class Preferences(views.generic.ListView):
admin = {}
def get(self, request):
ctx = self.admin.each_context(request)
return render(request, 'admin/preferences/preferences.html', ctx)
the template...
{% extends "admin/base_site.html" %}
{% block content %}
...HELLO WORLD!
{% endblock %}