How do I change the search field label in Django admin? - django

In Django admin.py
class TableAdmin(admin.ModelAdmin):
search_fields = ["name", "description", "category"]
list_display = ["name", "description", "category"]
admin.site.register(Table, TableAdmin)
Here, we can change the list_display, label name by using below code,
name.short_description = "Product Name"
Now the question is, How do I change the search fields label name?

You can override django admin template's search_form.html file.
create a new html file similar to it, and name it search_form.html:
{% load i18n static %}
{% if cl.search_fields %}
<div id="toolbar"><form id="changelist-search" method="get">
<div><!-- DIV needed for valid HTML -->
<label for="searchbar"><img src="{% static "admin/img/search.svg" %}" alt="Search" /></label>
<input type="text" size="40" name="{{ search_var }}" value="{{ cl.query }}" id="searchbar" autofocus />
<input type="submit" value="{% trans 'My Search' %}" />
{% if show_result_count %}
<span class="small quiet">{% blocktrans count counter=cl.result_count %}{{ counter }} result{% plural %}{{ counter }} results{% endblocktrans %} ({% if cl.show_full_result_count %}{% blocktrans with full_result_count=cl.full_result_count %}{{ full_result_count }} total{% endblocktrans %}{% else %}{% trans "Show all" %}{% endif %})</span>
{% endif %}
{% for pair in cl.params.items %}
{% if pair.0 != search_var %}<input type="hidden" name="{{ pair.0 }}" value="{{ pair.1 }}"/>{% endif %}
{% endfor %}
</div>
</form></div>
{%endif %}
Then put it in your template directory's admin folder like:
(template directory)>admin>search_form.html
And it should override the label of search button with text 'My Search'

Is it possible to override it just for specific admin model(s)? Yes this is possible.
In order to do so you need to create following files.
YourApp
-> templatetags
-> __init__.py
-> custom_tags.py
-> admin_overrides
-> InclusionAdminNode
Now you have to register a custom tag) which renders the Search Form.
from django.contrib.admin.views.main import (
SEARCH_VAR,
)
from django.template import Library
from ..admin_overrides.InclusionAdminNode import InclusionAdminNode
register = Library()
def search_form(cl):
"""
Display a search form for searching the list.
"""
return {
'cl': cl,
'show_result_count': cl.result_count != cl.full_result_count,
'search_var': SEARCH_VAR
}
#register.tag(name='search_custom_form')
def search_form_tag(parser, token):
return InclusionAdminNode(parser, token, func=search_form, template_name='custom_search_form.html',
takes_context=False)
Now, we have to use this tag in our YourApp/change_list.html.
{% block search %}{% search_custom_form cl %}{% endblock %}
Note: Now still you will see that there is no change in the form. But here is the catch we have to add our custom InclusionAdminNode and change the template path. By default it pick from admin.
from inspect import getfullargspec
from django.template.library import InclusionNode, parse_bits
class InclusionAdminNode(InclusionNode):
"""
Template tag that allows its template to be overridden per model, per app,
or globally.
"""
def __init__(self, parser, token, func, template_name, takes_context=True):
self.template_name = template_name
params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(func)
bits = token.split_contents()
args, kwargs = parse_bits(
parser, bits[1:], params, varargs, varkw, defaults, kwonly,
kwonly_defaults, takes_context, bits[0],
)
super().__init__(func, takes_context, args, kwargs, filename=None)
def render(self, context):
opts = context['opts']
app_label = opts.app_label.lower()
object_name = opts.object_name.lower()
# Load template for this render call. (Setting self.filename isn't
# thread-safe.)
context.render_context[self] = context.template.engine.select_template([
'YourApp/%s/%s/%s' % (app_label, object_name, self.template_name),
'YourApp/%s/%s' % (app_label, self.template_name),
'YourApp/%s' % (self.template_name,),
])
return super().render(context)
That's it we are good to go now.
Hope this is helpful.

In the previous solution (of Nitish Kumar) you also need to add to change_list.html:
{% load custom_tags %}

Related

Add Cancel/Close button to Django admin model editor

I would like to add a Cancel button to the django default admin model editor to enable going back to previous page in case users decide to cancel editing/creating a model. One Option to do that will be extending the 'admin/submit_line.html' and add a Cancel button to it.
However, the default django 'admin/submit_line.html' template already includes a 'Close' button as shown in the code snippet below.
<div class="submit-row">
{% block submit-row %}
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save">{% endif %}
...
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew">{% endif %}
{% if show_save_and_add_another %}
<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother">{% endif %}
...
{% if show_close %}{% trans 'Close' %}
{% endif %}
{% endblock %}
</div>
If I copied the above template and override the show_close variable to True, the Close button will be shown and closes the form as expected. But Isn't there a way to configure 'show_close' visibility from the models.py or admin.py classes?
override submit_line.html template with following content:
{% extends "admin/submit_line.html" %}
{% load i18n admin_urls %}
{% block submit-row %}
{{ block.super }}
{% if not show_close and adminform.model_admin.show_close_button %}
{% translate 'Close' %}
{% endif %}
{% endblock %}
and then in your admin class you can set field show_close_button (can choose anything you want) to True/False and close button will show/hide.
class MyAdmin(admin.ModelAdmin):
...
show_close_button = True
...
using adminform.model_admin.show_close_button you will get to that field in template.
You could define a custom AdminSite and override the method each_context to add show_close to all admin forms
def each_context(self, request):
context = super().each_context(request)
context['show_close'] = True
return context
Or you can override changeform_view on your model admins to set extra_context. You could have a base class that all the admins where you wanted this functionality inherited from
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['show_close'] = True
return super().change_view(self, request, object_id, form_url=form_url, extra_context=extra_context)
I was able to change these variables by overriding the ModelAdmin.changeform_view method:
class UserAdmin(ModelAdmin):
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context["show_save"] = False
extra_context["show_save_and_continue"] = False
extra_context["show_close"] = True
return super().changeform_view(
request,
object_id=object_id,
form_url=form_url,
extra_context=extra_context
)

How to dynamically call a Django templatetag from a variable

I'm trying to allow for dynamic template tags. Specifically, I have a menu setup that I'm defining in code vs templates. And I would like to render the menu label as {{ request.user }}. So how can I define that as a string in Python, and allow the template to parse and render the string as intended. And not just variables too, templatetags as well ({% provider_login_url 'google' next=next %}).
What am I missing?
Update with code:
I'm specifically designing my menus with django-navutils, but that's less important (basically the package just stores the defined data and then uses templates to render it).
from navutils import menu
top_horizontal_nav = menu.Menu('top_nav')
left_vertical_nav = menu.Menu('left_nav')
menu.register(top_horizontal_nav)
menu.register(left_vertical_nav)
sign_in = menu.AnonymousNode(
id='sign_in',
label='Sign In',
url='{% provider_login_url "google" next=next %}',
template='nav_menu/signin_node.html',
)
user = menu.AuthenticatedNode(
id='user_menu',
label='{{ request.user }}',
url='#',
template='nav_menu/username_node.html'
)
top_horizontal_nav.register(sign_in)
top_horizontal_nav.register(user)
What I would like to do, is now render these string values ('{{ request.user }}') in my templates
{% load navutils_tags %}
<li
class="{% block node_class %}nav-item menu-item{% if node.css_class %} {{ node.css_class }}{% endif %}{% if is_current %} {{ menu_config.CURRENT_MENU_ITEM_CLASS }}{% endif %}{% if has_current %} {{ menu_config.CURRENT_MENU_ITEM_PARENT_CLASS }}{% endif %}{% if viewable_children %} has-children has-dropdown{% endif %}{% endblock %}"
{% for attr, value in node.attrs.items %} {{ attr }}="{{ value }}"{% endfor %}>
<a href="{{ node.get_url }}" class="nav-link"{% for attr, value in node.link_attrs.items %} {{ attr }}="{{ value }}"{% endfor %}>{% block node_label %}{{ node.label }}{% endblock %}</a>
{% if viewable_children %}
<ul class="{% block submenu_class %}sub-menu dropdown{% endblock %}">
{% for children_node in viewable_children %}
{% render_node node=children_node current_depth=current_depth|add:'1' %}
{% endfor %}
</ul>
{% endif %}
</li>
So, for the above, where I'm rendering {{ node.label }}, how can I get the value stored in node.label to actually be parsed as a request.user? This similarly applies for the URL of value {% provider_login_url "google" next=next %}.
You can create a custom template tag and render those. Like this
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def render_nested(context, template_text):
# create template from text
tpl = template.Template(template_text)
return tpl.render(context)
Then in template
...
<a href="{{ node.get_url }}" class="nav-link"
{% for attr, value in node.link_attrs.items %} {{ attr }}="{{ value }}"{% endfor %}>
{% block node_label %}{% render_nested node.label %}{% endblock %}
</a>
...
Haven't tested but I think it will work.
More on template: https://docs.djangoproject.com/en/dev/ref/templates/api/#rendering-a-context
More on custom tags: https://docs.djangoproject.com/en/dev/howto/custom-template-tags/#writing-custom-template-tags
If I understand you well, you want to write template code that renders to template code and then be processed again. Something like ... meta-templates?
I can see you already figured out the first part, but now you need the resulting code to be parsed again in order to set the respective value for {{ request.user }}.
I achieve that by using middleware, but since parsing a template is an expensive operation I implemented some a mechanism to apply the "re-parsing" process just to urls/views of my selection.
The model.
class ReparsingTarget(models.Model):
url_name = models.CharField(max_length=255)
Simple enough, just a model for storing those URL names I want to be affected by the middleware.
The middleware.
class ReparsingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# We are no interested in whatever happens before
# self.get_response is called.
response = self.get_response(request)
# Now we will re-parse response just if the requested url
# is marked as target for re-parse.
if self._marked(request) and isinstance(response, TemplateResponse):
# Decode the template response code ...
content = response.content.decode('utf-8')
# Create a Template object ...
template = Template(content)
# Provide request to de context ...
context_data = response.context_data
context_data.update({'request': request})
# ... and renders the template back the the response.
response.content = template.render(Context(response.context_data))
return response
def _marked(self, request):
url_name = resolve(request.path_info).url_name
return ReparsingTarget.objects.filter(url_name=url_name).exists()
It is a really simple implementation, the tricky part for me was to figure out how to put the idea into Django code.
In practice.
Some model.
class Foo(models.Model):
label = models.CharField(max_length=255)
The template:
{% for foo in foos %}
{% comment %} Remember foo.label == '{{ request.user }}' {% endcomment %}
<p> Username: {{ foo.label }} </p>
{% endfor %}
The stored Foo instance:
And the result:

Check if text exists in Django template context variable

This may not be the best way of doing this (open to suggestions). But I want to display a button on my home page depending on the value of a Boolean in the custom user model.
I am passing the value of this boolean via context in the view. But I can't seem to get the template logic to work.
Models.py
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
isAdmin = models.BooleanField(default = False,)
#more models...
views.py
from django.views.generic import TemplateView
from django.contrib.auth import get_user_model
from accounts.models import CustomUser
class HomePageView(TemplateView):
template_name = 'home.html'
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated:
adminStatus = CustomUser.objects.get(id=self.request.user.id)
print(adminStatus.isAdmin)
context['adminStatus'] = str(adminStatus.isAdmin)
return context
home page template.html
{% extends 'base.html' %}
{% block body %}
{% if user.is_authenticated %}
<h4>Hi {{ user.username }}!</h4>
<a class="btn btn-primary btn-lg" href="{% url 'dashboard' %}" role="button"> Go to Dashboard</a>
{% else %}
<p>You are not logged in</p>
login
</div>
{% if adminStatus == "True" %}
<h1>test</h1>
<div class = "adminPanel">
<a class="btn btn-primary btn-lg" href="{% url 'newEquipment' %}" role="button"> add new equipment</a>
</div>
{% endif %}
{% endif %}
{% endblock %}
I can't see the "newEquipment" button even though the adminStatus context is equal to "True", as verified by the print() command.
I have a feeling my template logic is not correct. I also tried:
{% if adminStatus contains "True" %}
In the view, context['adminStatus'] is defined only when the user is logged in. Meanwhile in the template, you are checking for adminStatus when the user is not logged in.
First the return context statement needs to be un-indented once, so that context (with or without adminStatus) is available regardless:
def get_context_data(self, **kwargs):
context = super(HomePageView, self).get_context_data(**kwargs)
if self.request.user.is_authenticated:
adminStatus = CustomUser.objects.get(id=self.request.user.id)
context['adminStatus'] = adminStatus.isAdmin
return context
Next, yes you probably need to fix your template logic. Assuming you want to check for adminStatus only if the user is logged in, it should look like:
{% if user.is_authenticated %}
<h4>Hi {{ user.username }}!</h4>
...
{% if adminStatus %}
<h1>test</h1>
...
{% endif %}
{% else %}
<p>You are not logged in</p>
...
{% endif %}
Original answer:
In the view, you likely don't have to stringify adminStatus.isAdmin.
context['adminStatus'] = adminStatus.isAdmin
If passed to the context as a boolean, you should be able to use this expression in the template:
{% if adminStatus %}

Django select 2 multiple select slow load

I'm a beginner and I've been playing around with the multiple select option of select2.js. In a historical school database we have over 300k student_id's. I can get the select2 option to work, but it's extremely slow and takes forever to load. I've seen other pages with select 2 load massive amounts of data and work fine.
I'm using the following to javascript to load select2.
$(document).ready(function () {
$('.js-example-basic-multiple').select2();
});
In Django i'm loading the data in my template with:
<script src= "{% static '/search/user_select2.js' %}" type="text/javascript"></script>
<div class="col"><h4 style="margin-top: 0"><strong>Student ID List</strong></h4><select data-placeholder="Choose a list of 3-4 User ids..." class="js-example-basic-multiple" value = "{{ userid }}" style="width: 1110px" required>
{% for user in userid %}
<option value="{{ user }}"> {{ user }}</option>
{% endfor %}
</select>
userid is defined with the following arg in my view:
def multisearch(request):
userid = STudent.objects.filter(student_status_id = 1).values('studentid')
print(userid)
args = {'userid':userid}
return render(request, 'multisearch.html',args)
It takes a long time to load be cause you preload all options of your select2 input. I would suggest you to use select2 through django-autocomplete-light to avoid this issue. It will provide you tools to setup an autocomplete system and load matching options while typing text on your select2 input. Moreover, results paginated so that they are loaded as you scroll the select2 dropdown.
Basically you will have an autocomplete view:
from dal import autocomplete
from your_app.models import Student
class Status1StudentsAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Student.objects.filter(student_status_id=1)
if self.q: # self.q is the user-typed query
qs = qs.filter(name__istartswith=self.q)
return qs
That, of course, needs to be routed:
from your_app.views import Status1StudentsAutocomplete
urlpatterns = [
url(
r'^autocomplete/students/status-1/$',
Status1StudentsAutocomplete.as_view(),
name='students_status1_autocomplete',
),
]
Then use an autocomplete widget for your field:
from dal import autocomplete
from django import forms
class SomeForm(forms.Form):
student = forms.ModelChoiceField(
queryset=Student.objects.filter(student_status_id=1),
widget=autocomplete.ModelSelect2(url='student_status1_autocomplete')
)
Finally just display your form as you usually would and don't forget to include the custom css/js with {{ form.media }}.
{% extend "your_layout.html" %}
{% block extrahead %} {# assuming extrahead block is within the <head> element #}
{{ block.super }}
{{ form.media }}
{% endblock %}
{% block content %} {# assuming you have a content block within your <body> element #}
<form method="POST">
{% csrf_token %}
{{ form }}
<button type="submit">Go!</button>
</form>
{% endblock %}

Completely stripping certain HTML Tags in Django forms

I have a ModelForm that posts news items to a database, and it uses a javascript textarea to allow the authorized poster to insert certain pieces of HTML to style text, like bold and italics. However, since I have the template output using the "safe" filter, it outputs all the HTML the form widget tries to pass on. This includes a bothersome <br> tag that never goes away, making it so you can submit without form validation reading the field as empty and stopping you. How can I make that I can not only filter the <br> tag, but completely remove it from the data? Here is relevant code:
Models.py:
from django.db import models
from django.forms import ModelForm, forms
from django.contrib.auth.models import User
# Create your models here.
class NewsItem(models.Model):
user = models.ForeignKey(User)
date = models.DateField(auto_now=True)
news = models.TextField(max_length=100000, blank=False, help_text='HELP TEXT')
def __unicode__(self):
return u'%s %s %s' % (self.user, self.date, self.news)
class NewsForm(ModelForm):
class Meta:
model = NewsItem
exclude=('user','date',)
Views.py:
from news.models import NewsForm, NewsItem
from django.shortcuts import render
from django.http import HttpResponseRedirect, HttpResponse
def news(request):
if request.method == 'POST':
item = NewsItem(user=request.user)
form = NewsForm(request.POST, instance=item)
if form.is_valid():
form.save()
return HttpResponseRedirect('/news/')
else:
form = NewsForm()
news_list = NewsItem.objects.all()
return render(request, 'news_list.html', {'news_list': news_list, 'form': form})
news_list.html:
{% extends "base.html" %}
{% block title %}News in the Corps{% endblock %}
{% block content %}
<h2 id="page_h">News in the Corps</h2>
{% if user.is_authenticated %}
<h3>Post News</h3>
<script src="{{ STATIC_URL }}nicEdit.js" type="text/javascript"></script>
<script type="text/javascript">bkLib.onDomLoaded(nicEditors.allTextAreas);</script>
<div id="news_poster">
<form id="news_poster" action="/news/" method="POST">{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
</div>
{% endif %}
<ul id="events_list">
{% if news_list %}
<div id="news_list">
{% for news in news_list %}
{% if news.id == 1 %}
<hr />
{% endif %}
<div id="{{ news.id }}" class="news_item">
<p class="poster">Posted By: {{ news.user }} | Posted On: {{ news.date }} | Link</p>
<div id="news_item">
{{ news.news|safe }}
</div>
</div>
<hr />
{% endfor %}
</div>
{% endif %}
</ul>
{% endblock %}
You can try the removetags template filter:
{{ news.news|removetags:"br"|safe }}
I can't help but thinking that the "removetags" as Timmy O'Mahony suggested might work if it was structured like this:
{{ news.news|safe|removetags:"br"}}
Give it a shot and see if it works. I would reply, but my karma's not height enough to directly reply to an answer with a suggestion.