How to add custom view to django admin interface? - django

My django admin interface looks like this:
Now I would like to add a view which does not correspond to a model.
I could overwrite the template of above page and add a custom link. But I think this would look ugly.
Example for overwriting admin/index.html:
{% extends "admin/index.html" %}
{% block content %}
{{ block.super }}
<div class="app-sonstiges module">
....
</div>
{% endblock %}
But maybe there is an official way to do add a custom view to the admin interface?
In my case I want to provide a form which can execute tcptraceroute to a remote server. The admin of my app needs this.
I used the same html tags. Now the link "tcptraceroute" looks nice, but unfortunately the messages moved down:
Is there a way to get a custom part like "Sontiges ... tcptraceroute" like in the screenshot, without moving the latest actions down?
Here is how the html structure looks like. My <div class="app-sonstiges"> is below content-main:

You have 3 options here:
Using third-party packages which provide menu
This is pretty straight forward, there are some good packages out there which support menu for admin, and some way to add your item to the menu.
An example of a 3rd party package would be django-admin-tools. The drawback is that it is a bit hard to learn. Another option would be to use django-adminplus but at the time of writing this, it does not support Django 2.2.
Hacking with django admin templates
You can always extend admin templates and override the parts you want, as mentioned in #Sardorbek answer, you can copy some parts from admin template and change them the way you want.
Using custom admin site
If your views are supposed to only action on admin site, then you can extend adminsite (and maybe change the default), beside from adding links to template, you should use this method to define your admin-only views as it's easier to check for permissions this way.
Considering you already extended adminsite, now you can use the previous methods to add your link to template, or even extend get_app_list method, example:
class MyAdminSite(admin.AdminSite):
def get_app_list(self, request):
app_list = super().get_app_list(request)
app_list += [
{
"name": "My Custom App",
"app_label": "my_test_app",
# "app_url": "/admin/test_view",
"models": [
{
"name": "tcptraceroute",
"object_name": "tcptraceroute",
"admin_url": "/admin/test_view",
"view_only": True,
}
],
}
]
return app_list
You can also check if current staff user can access to module before you show them the links.
It will look like this at then end:

Problem here that your div is not inside main-content. I suggest extending admin/index.html
Put in app/templates/admin/index.html
{% extends "admin/index.html" %}
{% load i18n static %}
{% block content %}
<div id="content-main">
{% if app_list %}
{% for app in app_list %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
{{ app.name }}
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
{% if model.view_only %}
<td>{% trans 'View' %}</td>
{% else %}
<td>{% trans 'Change' %}</td>
{% endif %}
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
<!-- here you could put your div -->
<div class="app-sonstiges module">
....
</div>
<!-- here you could put your div -->
{% else %}
<p>{% trans "You don't have permission to view or edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
The another approach is to add custom app to app_list. But it is far more ugly. So I suggest overriding template.
I assume you are overriding get_urls in AdminSite.

If you need a custom page to handle user input (from form), you may want to give django-etc 1.3.0+ a try:
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()

Related

How to reassign a model to a different app for display only purposes in Django Admin

I know I can change the display title for a model in Django Admin using
class Meta:
verbose_name='Custom Model Name Here'
However, is there a way to display which app heading a model is displayed under?
For example, if I create a custom user model Users in a new app also called users then the default user model goes from Authentication and Authorization > Users to Users > Users.
I would like to retain it under the original heading Authentication and Authorization > Users.
I have read this answer which suggests changes the app verbose_name, however it only changes the verbose name of the app associated with the model. I want to show the model in a different group on the admin panel. You can see the issue that approach takes here:
You could set up a proxy model
In app_1.models.py
class MyModel(models.Model):
...
In app_2.models.py
from app_1.models import MyModel
class MyModelProxy(MyModel):
class Meta:
proxy = True
Then register the MyModelProxy in admin as normal
There is no simple way of doing what you're intending, check out the Django code/template that generates the view:
#never_cache
def index(self, request, extra_context=None):
"""
Display the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""
app_list = self.get_app_list(request)
context = {
**self.each_context(request),
'title': self.index_title,
'app_list': app_list,
**(extra_context or {}),
}
request.current_app = self.name
return TemplateResponse(request, self.index_template or 'admin/index.html', context)
templates/admin/index.html:
{% for app in app_list %}
<div class="app-{{ app.app_label }} module">
<table>
<caption>
{{ app.name }}
</caption>
{% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}">
{% if model.admin_url %}
<th scope="row">{{ model.name }}</th>
{% else %}
<th scope="row">{{ model.name }}</th>
{% endif %}
{% if model.add_url %}
<td>{% trans 'Add' %}</td>
{% else %}
<td> </td>
{% endif %}
{% if model.admin_url %}
{% if model.view_only %}
<td>{% trans 'View' %}</td>
{% else %}
<td>{% trans 'Change' %}</td>
{% endif %}
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
As you can see, Django admin template is looping your app_list directy, so the only way of doing it would be to override the admin/index.html template to place your models in your desired order.
From Django docs:
For those templates that cannot be overridden in this way, you may still override them for your entire project. Just place the new version in your templates/admin directory. This is particularly useful to create custom 404 and 500 pages.
https://docs.djangoproject.com/en/2.2/ref/contrib/admin/#overriding-vs-replacing-an-admin-template
In firstapp.models.py
class ActualModel(models.Model):
class DisplayModel(ActualModel):
class Meta:
proxy = True
in secondapp.admin.py
from firstapp.models import DisplayModel
admin.site.register(DisplayModel)
Easier way to do that:
In model class Meta add
app_label = 'app2'
db_table = 'app1_modelname'
so you assigning model to another app with app_label and assigning db_table with old (current) table name to prevent making migrations and table renami

Custom input field and custom searchqueryset

I followed the get started doc and everyting work well! :)
But i would like to replace the form in the /search/search.html by a custom form without selectable model checkbox.
In the form i would like to add a button which on click, order search results by a criteria
My questions are:
Which files i need to create or modified to perfoms that and what are their roles?
My codes are:
search_indexes.py
from haystack import indexes
from models import ProduitCommerce
class ProduitIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
commerce = indexes.CharField(model_attr = 'nom_commerce')
nom = indexes.CharField(model_attr = 'nom_produit')
price = indexes.DecimalField(model_attr = 'prix') #Field to filter ON
def get_model(self):
return ProduitCommerce
search/search.html
{% extends 'base.html' %}
{% block content %}
<h2>Search</h2>
<form method="get" action=".">
<table>
{{ form.as_table }} <!------ FORM TO CHANGE BY A CUSTOM FORM BLOCK TO INCLUDE WITH DJANGO ------->
<tr>
<td> </td>
<td>
<input type="submit" value="Search">
</td>
</tr>
</table>
{% if query %}
<h3>Results</h3>
<!---------------------------- PLACE TO INCLUDE THE BUTTON TO FILTER ON result.object.prix------------------>
{% for result in page.object_list %}
<p>
{{ result.object.nom_produit }}{{result.object.prix}}
</p>
{% empty %}
<p>No results found.</p>
{% endfor %}
{% if page.has_previous or page.has_next %}
<div>
{% if page.has_previous %}{% endif %}« Previous{% if page.has_previous %}{% endif %}
|
{% if page.has_next %}{% endif %}Next »{% if page.has_next %}{% endif %}
</div>
{% endif %}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
</form>
produitcommerce_text.txt
{{ object.nom_produit }}
{{ object.nom_commerce }}
{{ object.description }}
{{ object.prix }}
PS: I'm working on a django project with the 1.5 .1 version and whoosh like haystack backend.
Thanks for you help :)
You are getting the selectable model checkboxes because you're extending the ModelSearchForm:
#forms.py
from haystack.forms import ModelSearchForm
class ProduitForm(ModelSearchForm):
You can easily avoid this behaviour by extending the simple search form variant, provided by the haystack:
#forms.py
from haystack.forms import SearchForm
class ProduitForm(SearchForm):
Then you're going to add something like the following to you application's urls.py:
#urls.py
from haystack.query import SearchQuerySet
from haystack.views import SearchView, search_view_factory
from myapp.models import Produit
urlpatterns = patterns('',
url(r'^produit/search/$', search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=SearchQuerySet().models(Resume),
form_class=ProduitForm
), name='produit_search'),
)
Please note that there is a bug with .models() filter when trying to use it with latest versions of Haystack and Whoosh. If you would experience any kind of problems with the strategy above, then you should make sure, that your Haystack and Whoosh are of versions 2.0.0 and 2.4.1 relatively - tested and works well.
Also, not related to your question, you might want to avoid using HAYSTACK_SEARCH_RESULTS_PER_PAGE in your settings.py. It also has a very ugly bug. Just sharing my experience. Note that this info is related to Whoosh, everything should work fine with any other backend.

How to check if current visitor is shop's admin?

I would like to create a product that will be available in Shopify's storefront but only accessible for the shop administrator. Is there a way to identify if the current user is an admin via liquid? Or is there any other solution for this. Thanks!
If you're signed in as an admin, when rendering the {{ content_for_header }} include, it will contain some JavaScript to push the page content down to make room for the Shopify admin bar.
We can utilize capture to store the {{ content_for_header }} code as a liquid variable and then use the contains operator to check if admin_bar_iframe exists in the variable.
{% capture CFH %}{{ content_for_header }}{% endcapture %}{{ CFH }}
{% if CFH contains 'admin_bar_iframe' %}
{% assign admin = true %}
{% endif %}
{% if admin %}
<!-- User is an admin -->
{% else %}
<!-- User is not an admin -->
{% endif %}
Note: I've noticed that the Shopify admin bar doesn't populate at all times (I think its a bug). If your Shopify admin bar is not populating on your instance this will not work.
Just figured out this method that works. (Basically the same thing as the old method, just another way around!)
Detecting logged in admin viewing site:
{% if content_for_header contains 'adminBarInjector' %}
<script>
console.log("You're a logged in admin viewing the site!");
</script>
{% endif %}
Detecting admin in design mode:
{% if content_for_header contains 'designMode' %}
<script>
console.log("You're an admin in design mode!");
</script>
{% endif %}
Another approach would be to use Customer Accounts. Liquid provides a {{ customer }} object, which is only present when a user (customer) is logged in.
You can add a specific tag to an admin user and use liquid to verify if a tag is present:
{% if customer.tags contains "admin" %}
And of course you need to identify your product as 'admin-only', for example using tags:
{% if customer.tags contains "admin" and product.tags contains "admin" %}
<!-- render product -->
{% else %}
<!-- do nothing -->
{% endif %}
EDIT: This seems not to be working anymore since an update to Shopify.
I know this is late, but here is what I've used and it has worked correctly in my testing. This is adapted from the previous answers, but allows use for Customise Theme options, etc.
{% capture CFH %}{{ content_for_header }}{% endcapture %}{{ CFH }}
{% assign isAdmin = true %}
{% if CFH contains '"__st"' %}
{% if CFH contains 'admin_bar_iframe' %}{% else %}
{% assign isAdmin = false %}
{% endif %}
{% endif %}
This is a more complete version of that provided by kyle.stearns above (I can't comment on it because new). One of the main instances where admin bar doesn't load is when previewing themes. Here is the amended code which I've used (updated June 2018 - as Shopify edited the way we preview themes):
{% capture CFH %}{{ content_for_header }}{% endcapture %}
{% if CFH contains 'admin_bar_iframe' %}
{% assign admin = true %}
{% elsif CFH contains 'preview_bar_injector-' %}
{% assign admin = true %}
{% endif %}
{% if admin %}
<!-- User is an admin -->
<script>
alert ("do some work");
</script>
{% else %}
<!-- User is not an admin -->
<script>
alert ("please buy some stuff");
</script>
{% endif %}
If you're using Plus then you also have access to the User via api.
I know this is an old question, but it still shows on top in google searches.
There's now a much better way.
Using liquid:
{% if request.design_mode %}
<!-- This will only render in the theme editor -->
{% endif %}
Using javascript:
if (Shopify.designMode) {
// This will only render in the theme editor
}
Source: shopify.dev
Currently there isn't. You could perhaps try inspecting cookies and stuff to see if there's some identifying information that would let you know if the user is an admin, but it would be fragile.
This would also require rendering the items, but hiding them via CSS. Then you'd show them using JS after you've run your checks.
As stated, this would probably be really fragile.

How to add top save button on django admin change list?

I want to have a save button on top of django admin change list page. It seems that django don't have this functionality built-in. The save_on_top option only controls behavior on change form page. Any suggestion is welcome.
In Django 3 (and maybe earlier, not sure) in your custom admin form add save_on_top = True
class MyAdmin(admin.ModelAdmin):
save_on_top = True
First, you need a way to extend the template found at django/contrib/admin/templates/admin/change_list.html. If you don't already know how to do that, check out this answer and this answer.
Next, you need to create your own change_list.html template and put code similar to the following in it. For the sake of simplicity, I've included inline CSS. However, this is bad practice, so you should not do it. Assuming you move the CSS to an external file, you won't need to load admin_static. Lastly, the extends line that you use might not be exactly the same as what I've shown here.
{% extends "contrib/admin/templates/admin/change_list.html" %}
{% load i18n admin_static %}
{% block result_list %}
{% if cl.formset and cl.result_count %}
<div style="border-bottom: 1px solid #ccc; background: white url({% static "admin/img/nav-bg.gif" %}) 0 180% repeat-x; overflow: hidden;">
<p>
<input type="submit" name="_save" class="default" value="{% trans 'Save' %}"/>
</p>
</div>
{% endif %}
{{ block.super }}
{% endblock %}
The {% if %} tag and the <input> tag inside of it is from django/contrib/admin/templates/admin/pagination.html.
The CSS is based on the CSS for #changelist .paginator and is found in django/contrib/admin/static/admin/css/changelists.css.
If you don't mind having pagination links at the top of the page as well, you can do it with a few lines of template code, worksforme in Django 2.0.
Create my_app/templates/admin/my_app/my_model/change_list.html:
{% extends "admin/change_list.html" %}
{% load admin_list %}
{% block result_list %}
{% pagination cl %}
{{ block.super }}
{% endblock %}
This will render the pagination and the save button:
Could benefit from a line or two of CSS, though...

Django change the admin page label 'Auth' to 'Authentication'

How can i change the display label Auth in the Django admin dashboard to Authentication?
There is currently no easy/elegant way to do this. Customisable app-labelling has been a sore point for some time. You can override admin/index.html and inject some javascript code to change the labelling. Note that you could also change admin.site.index_template to something like "admin/my_index.html", which can then use {% extends "admin/index.html" %} to keep things DRYer.
Of course there are other areas in the admin that "Auth" will appear also, such in "admin/app_index.html", the breadcrumbs, etc...
I imagine if your overwriting admin/index.html you could hard code the logic in the template instead of any javascript:
<caption><a href="{{ app.app_url }}" class="section">
{% ifequal app.name "Auth" %}
{% trans 'Authentication' %}
{% else %}
{% blocktrans with app.name as name %}{{ name }}{% endblocktrans %}
{% endifequal %}
</a></caption>