Django Admin: using different templates for two admin site - django

I've a Django project with two different admin-site (as described in documentation )
I would like to have different custom template for each of them.
I know how to override custom template, by putting html files in myproject/templates/admin/ directory.
However, both admin-site use those templates !
I don't understand how to specify another set of custom templates.
Ideally, I would like having:
# For first admin site
myproject/templates/admin-a/
base.html
base_site.html
and:
# For second admin site
myproject/templates/admin-b/
base.html
base_site.html

first option will be to have two ModelAdmin classes, one derived from second one, with some additional parameters defining templates, here is part of the admin code:
# Custom templates (designed to be over-ridden in subclasses)
add_form_template = None
change_form_template = None
change_list_template = None
delete_confirmation_template = None
delete_selected_confirmation_template = None
object_history_template = None
above variables can be set in your admin class.
second way is to pass a base template name into the template, and then use this (variable) as a parameter to the extends template tag. Documentation here.
third option wil be to have a two instances of code running, but with two configs with different setting variable TEMPLATE_DIRS, first one e.g.:
TEMPLATE_DIRS = ('templates-a',)
second
TEMPLATE_DIRS = ('template-b', 'template-a')
Having both template dirs here gives you an fallback option, so you will define only those templates which are different.
Third option is easiest to implement (no change to the code) but it requires 2 separated instances working at the same time (more system resources consumed).

Related

Is it possible to give external URLs names in django

I have just started naming my URL patterns in Django, so that if I want to change the URL pattern, I just have to change it in one place. e.g:
url(r'^$', HomeListView.as_view(), name='home'),
And referencing it in my templates like this:
{% url home %}
Is this possible with external links in case they change or I change the a Facebook page link for example. How would this look?
Thanks
One way to do this could be to write an external_url template tag, and have an ExternalURL model that stores them.
This would give you the advantage of being able to have the urls editable without redeploying changed code.
The disadvantage is that there will be database lookups to see those urls. Also, you would need to {% load external_urls %} in templates you want to use it in.
# models.py pseudo-code
class ExternalURL(models.Model):
name = models.CharField(unique=True)
url = models.URLField()
Your template tag might look something like:
# app/templatetags/external_url.py
#library.register()
def external_url(name):
try:
return ExternalURL.objects.get(name=name).url
except ExternalURL.DoesNotExist:
return ''
Another alternative could be to have a Context Processor that stores them all in the context, allowing you to not have to pass them explicitly into views: would be useful if you had several external urls that were used in many places within your system.
def external_urls(request):
return {
'google': 'http://www.google.com/',
# more here
}
The advantages of this is no database lookup, no requirement to load the template tag, but you will need to add it to your settings.CONTEXT_PROCESSORS. Also, you could inspect request to see if the current user may see all the urls.
If its external links, and possibility of the change then you should define it in settings or separate static url file and pass those variable with request context.
urls.py should be recommended to use only for your app specific urls.
In this SO thread you can see how to approach defining constant

Conditional tags in templates

I'm using Django on top of an existing database/user framework so unfortunately it is not possible to me for use the django auth framework.
I have my custom auth library built, now I just need to figure out how to use it in templates. Certain links should only appear for users who have access to it.
In PHP I could do soemthing like this
<?php if auth('RestrictedLinkName') {?> <?php } ?>
What is the Django way? I have been playing with custom tags but haven't been able to get it working yet. I was thinking something like:
{% if check_permission('Restrictedarea') %} hjkfgdkhfg {% endif %}
If you need to evaluate check_permission('Restrictedarea') then do it in the view function and pass the evaluated result in the template.
The djangobook says (in Chapter 4: Templates, in Philosophies and Limitations section) -
Business logic should be separated from presentation logic. Django’s
developers see a template system as a tool that controls presentation
and presentation-related logic – and that’s it. The template system
shouldn’t support functionality that goes beyond this basic goal.
For that reason, it’s impossible to call Python code directly within
Django templates. All “programming” is fundamentally limited to the
scope of what template tags can do. It is possible to write custom
template tags that do arbitrary things, but the out-of-the-box Django
template tags intentionally do not allow for arbitrary Python code
execution.
And if need to write custom tags check these links:
Writing a Template Tag in Django
Custom template tags and filters
You can write your custom template tags and filters. But this may help you up to a point, for further, you must write your custom context_processors (and custom middlewares if needed.)
But doing authentication and permission check through custom function is quite hard, but possible. I have a system fully runs on custom authentication/authorization.
First of all, you can examine django context_processors to understand how they work. then you can write your custom context_processor. After you add your context processor in your settings, you can use those methods, and do your authentication/authorization as you wish.
One of my custom contect_processor function is like:
from django.utils.functional import lazy
def CustomProcessor(request):
cust_perms = {
'admin_perm_check': lazy(lambda: myCustomPermChecker(request), myCustomPermChecker)(),
'system_admin': aFunctionToReturnBoolValue(),
}
return custom_perms
class myCustomPermChecker(object):
def __init__(self, request):
self.request = request
def __getitem__(self, perm_name):
return True if (perm_name in user_perm_list()) else False
In your template
{%if admin_perm_check.perm_name%}...{%endif%}
{%if system_admin %} this is a bool check{%endif%}
You must define your permchecker as a class, which have __getitem__ method so, your template tag admin_perm_check.perm_name could work. This perm checker accepts only one additional paramater (perm_name), If you want to use django-style two parameter check then you must do:
class myCustomPermChecker(object):
def __init__(self):
pass
def __getitem__(self, module_name):
return SecondPermCheckerStep(module_name)
class SecondPermCheckerStep(object):
def __init__(self, module_name):
self.module_name = module_name
def __getitem__(self, perm_name)
return True if ('%s.%s' % (self.module_name,perm_name) in user_perm_list()) else False
{%if admin_perm_check.module_name.perm_name%}
You can use another class.__getitem__ to add one more key to tour tag chain in your template etc.
Since your context_processor CustomProcessor(request): accept http.request object as a parameter, you can pass your methods or functions any value (session id or user id etc.) that will be required in authenticaon or authorization. You can write a middleware to set custom values to your request object to use (like django set user instance and let you use request.user in your views.). You can also set your custom user model instance so you can use it in you template (if you set it in your request_context) and in your views (if you set it in your middleware)
Reading docs could not help here much, better is checking django code to see how django handle this.

How to make a dynamic menu in base template using django

I'm about to start a new project and I think this time django is the way to go. I've been reading the documentation for the past two weeks and it looks promissing.
Ok, the thing is that I could not find anything about (in C# MVC called) Partial Rendering. For example if I want a dynamic menu where the menu-items comes from the database, then I would expect that the base template (or master page) renders the menu on each request (the partial renderer invokes another action or renders a template with some session data). So, the menu comes for free as long as my template inherits from this base template.
Honestly, I have no clue on how to achieve this.
What I would like is some code in the base template that uses data that is not contained in the child template. I don't want to include an extra variable (maybe 'menu_list_items') every time I call render_to_response('child_content.html',context). Is this possible?
Thanks!
You could either use a context processor, or a custom template tag to provide this functionality.
A context_processor is a simple function which can add objects to every RequestContext. A custom template tag can have its own template snippet and context which could render the menu for you.
For the template reusing: you should just create a base template for the generic layout, and use detailed templates for the individual pages. This is already covered in detail by the Django documentation.
What I tend to do for those generic parts (say for example, a menu highlighting the current part of the site the use is on), is to create my own render_to_response functions, akin to the following:
from django.shortcuts import render_to_response as django_render_to_response
def render_to_response(template, params, context_instance):
return django_render_to_response(template,
AppendStandardParams(request, params),
context_instance)
The ApplyStandardParams method then configures the menu based on the current path:
def AppendStandardParams(request, params):
if request.META['PATH_INFO'].startswith('/customer'):
params['category'] = 'customer'
params['title'] = 'Customer overview'
# and so on for all different parts
These category and title tags in this example are some values used to highlight the menu, configure titles, and so on. For example:
<!-- Customer menu entry: change class if this is the current category. -->
<li{% if category == "customer" %} class="selected"{% endif %}>Customers</li>
Finally, to use it in a view, instead of the normal render_to_response import, I just do something like from lib.views import *, which makes my custom version available in the view. This way the syntax of all code in the views stays the same, but I don't have to customize the menu every time I create a new view or app.

How can you use Django template tags in a Jinja2 template?

There are a number of off-the-shelf Django packages that offer template tags, and I'd like to use them in my project despite the fact that it uses Jinja2.
I've seen some workarounds that allow other template engines to use Django tags. These involve creating a mini-template in a string, and passing it to the Django template processor along with the current context. Here are two example of what I'm talking about: Mako templates using Django template tags and Jinja2 templates using Django template tags.
I'm wondering if there's a less hackish solution.
what about moving problem to python's scope
and importing one python's function into another, this way:
in your_jinja_templatetags.py
from some_django_templatetags import somefilter as base_somefilter
#library.filter
def somefilter(value):
return base_somefilter(value)
Unfortunately Django template tags are not directly translatable to Jinja.
Django Template tags can do many different things, and are usually implemented as functions. Fortunately, Jinja can register these functions as globals and call them. This works for "simple" template tags out of the box.
If you are using the official Django Jinja2 backend, see https://docs.djangoproject.com/en/4.1/topics/templates/#django.template.backends.jinja2.Jinja2 to find out how to register a function (in this case your template tag) as a global. Once this is done, you can call the function from within your template as {{ your_tag_function() }}. Remember to pass whatever arguments the template tag function needs!
I like to use the https://github.com/niwinz/django-jinja backend, myself- if you are using this backend, see https://niwi.nz/django-jinja/latest/#_custom_filters_globals_constants_and_tests for how to register a function as a global.
Unfortunately, this only works for "simple" tags out of the box. Some tags are "inclusion tags" or otherwise depend on the Django Template Engine. Since Django Templates can't be rendered within the Jinja engine, you have to do a bit more work to translate these types of tags. You will need to look at the source code to tell the difference and see what they are doing.
What I usually do for the more complicated tags is create a "jinja_functions.py" script in my Django app, then in that script I import the original template tag. Then I write a function that wraps it- calling the original function, getting back usually a dictionary, and then using that dict to render a string which is returned... most tags are simple enough I just use an f string or .format to format them with the results of the dict.
Then I register my new wrapper function as a jinja global and use it in my jinja templates!
The following is an example of where I re-implemented the template tags provided by https://github.com/timonweb/django-tailwind for use with jinja. I combined both the tailwind_css and tailwinf_preload_css tags into a single function, tailwind_css, which I register with Jinja as a global as per the instructions above, then I can call that function as {{ tailwind_css(prefetch=True) }} or {{ tailwind_css() }} within my templates.
from django.templatetags.static import static
from tailwind import get_config
from tailwind.utils import is_path_absolute
def tailwind_css(v=None, prefetch=False):
tailwind_css_path = get_config("TAILWIND_CSS_PATH")
if not is_path_absolute(tailwind_css_path):
tailwind_css_path = static(tailwind_css_path)
if v:
tailwind_css_path = f"{tailwind_css_path}?v={v}"
if prefetch:
rel = 'rel="preload"'
as_str = ' as="style"'
else:
rel = 'rel="stylesheet"'
as_str = ""
return f'<link {rel} href="{tailwind_css_path}"{as_str}>'
In conclusion it works well out of the box for "simple" tags because you can just register them as global functions in Jinja... but for "include" tags or other tags that rely on the Django Template engine, you will have to write your own function :( But usually its not too much work!

Styling certain admin change list rows

Is there a straightforward, common way to apply custom styling on admin change list element depending on its properties?
update
To be more precise: let's say I have a simple model object.
Foo
field1
field2
field3
#property
property1()
#property
property2()
ModelAdmin.list_display is defined as a subset of the available fields, so not every attribute (field/property) is displayed in the change list table.
I'd like to apply custom CSS class to the object's row when certain condition is fulfilled, for example: if foo_instance.property1 is True then add class bar to the corresponding tr element.
Now copy the template admin/base_site.html from within the default Django admin template directory (django/contrib/admin/templates) into an admin subdirectory of whichever directory you're using in TEMPLATE_DIRS. For example, if your TEMPLATE_DIRS includes "/home/my_username/mytemplates", as above, then copy django/contrib/admin/templates/admin/base_site.html to /home/my_username/mytemplates/admin/base_site.html. Don't forget that admin subdirectory.
Note that any of Django's default admin templates can be overridden. To override a template, just do the same thing you did with base_site.html -- copy it from the default directory into your custom directory, and make changes.
from django's tutorial
What exactly do you mean by "change list element" and "it's properties"? Using CSS 2 or CSS 3 selectors you can do some things. Otherwise, you might be able to do it easily using jQuery (or whatever). Since it is merely presentation related, I think this would be the cleanest solution.
Old question but if you stumble across it, the following tips might be helpful.
You can use django-liststyle to customise your admin changelist rows.
It's quite simple to implement your example:
class FooAdmin(admin.ModelAdmin, ListStyleAdminMixin):
...
def get_row_css(self, obj, index):
if obj.property1:
return 'bar'
return ''
Django Suit (not free) also offers "List row and cell attributes" style customisation