Can't get information from codeforces API in Django - django

in my project, I need to show the name of the contest site. I can't hardcode the name as I have to load the name & details of various websites as per user need. I need to show the Contest site name, contest name, contest date & time(found in UNIX format in the API which has to be converted), contest URL (which is in the list of return items but not showing while I open the API)
I am so new in Django and working for the first time with API
I wrote the function in views.py
def homepage(request):
response = pip._vendor.requests.get('https://codeforces.com/api/contest.list').json()
return render(request,'home.html',response)
and in HTML I did this to get the name of all contests
<div class="box">
{% for i in response %}
{{i.name}}
{% endfor %}
</div>

I think the problem is not related to Django but to how do you handle JSON in particular, in your view the API from request(I presume you are using the requests package) will return JSON
let's take a look at what the API returning:
{
"status": "OK",
"result": [
{
"id": 1768,
"name": "Codeforces Round (Div. 2)",
"type": "CF",
"phase": "BEFORE",
"frozen": false,
"durationSeconds": 7200,
"startTimeSeconds": 1672929300,
"relativeTimeSeconds": -2433104
},
{
"id": 1770,
"name": "Good Bye 2022",
"type": "CF",
"phase": "BEFORE",
"frozen": false,
"durationSeconds": 9000,
"startTimeSeconds": 1672410900,
"relativeTimeSeconds": -1914704
},
...
]
}
so in the JSON response, the detail you need is the value of result, this value is an array of dictionaries. Looping through the list and you can get the value
from django.utils.timezone import make_aware
from datetime import datetime
def homepage(request):
response = pip._vendor.requests.get('https://codeforces.com/api/contest.list').json()
# convert timestamp to readable datetime string
for contest in response.result:
contest["start_time"] = make_aware(datetime.fromtimestamp(contest["startTimeSeconds"]))
return render(request,'home.html',response)
and in your template:
<div class="box">
{% for i in response.result %}
<h3>Name</h3>: <p>{{ i.name }}</p>
<h3>Date and time</h3>: <p>{{ i.start_time|date:'Y-m-d H:i' }}</p>
{% endfor %}
</div>
for the date time format, follow the Django docs
I don't see anything related to contest URL in your API though

Related

Is there anything i missed in configuring django-rest-framework-datatables, getting error "DataTables warning: table id=test - Ajax error"

I tried to configure datatables with rest frame work, i'm getting error when page loads all the datatables fields like pagination, search and title is showing but no data is showing. what might be the reason?
serializers.py
class PermissionSerializer(serializers.ModelSerializer):
class Meta:
model = Permission
fields = (
'name', 'code', 'app',
)
views.py
from rest_framework import viewsets
from .serializers import PermissionSerializer
class PermissionViewSet(viewsets.ModelViewSet):
queryset = Permission.objects.all()
serializer_class = PermissionSerializer
class ViewallPerms(View):
def get(self, request):
context = {
'a' : 'a',
}
return render(request, 'flamika_admin/view_allpermissions.html', context)
urls.py
url(r'^perms/$', views.PermissionViewSet, name='perms'),
path('all-perms', login_required(views.ViewallPerms.as_view(), login_url='f_admin:admin_login'), name='all-perm'),
view_allpermissions.html
<script src="https://code.jquery.com/jquery-1.8.0.min.js"></script>
<div class="row">
<div class="col-sm-12 col-xs-12">
<table id="test" class="table table-striped table-bordered" style="width:100%">
<thead>
<tr>
<th>Code</th>
<th>Name</th>
<th>App</th>
</tr>
</thead>
</table>
</div>
</div>
<script>
$(document).ready(function() {
var table = $('#test').DataTable({
"serverSide": true,
dataSrc: "",
"ajax": "{% url 'flamika_admin:perms' %}",
"columns": [
{"data": "name"},
// Use dot notation to reference nested serializers.
// This data: could alternatively be displayed with the serializer's ReadOnlyField as well, as seen in the minimal example.
{"data": "code"},
{"data": "app"},
]
});
$('.btn-decade').on('click', function() {
table.columns().search('');
var rel = $(this).attr('rel');
if (rel) {
table.columns(3).search('^' + rel + '[0-9]$', true).draw();
} else {
table.draw();
}
});
$('#albums_minimal').DataTable({
"search": {"regex": true},
"language": {"searchPlaceholder": "regular expression"}
});
});
</script>
please let me know where i went wrong, is this right way to configure server-side with datatables. Please correct me. I wanted to display all data in that model.
You forgot to add "?format=datatables" in your ajax API call. Datatables expect response in a certain way to display it properly and if you have correctly configured django-rest-framework-datatables(follow the docs, its pretty much straightforward), the library checks if the format parameter is set to datatables or not. In case its not set, the default pagination format of DRF is used(which does not work with datatables, your case) where if set then django-rest-framework-datatables formats the response in the correct way for datatables.
Note: So i wanted to mention if you would like to use django's reverse feature even in js file there is a good library i will link https://github.com/ierror/django-js-reverse.
Use this to reverse and add ?format=datatables to the url or you could write the url manually.
E.g.
"ajax": {
"url": "http://127.0.0.1:8000/perms/?format=datatables",
"type": "GET",
"headers": {
/* any headers */
},
"dataSrc": "Mention your data source from the respone "
}
Check the url there, make sure its right. I have just written that as an example.
Update:
The url routing for PermissionViewSet was incorrect.
Correct form is:
url(r'^perms/$', PermissionViewSet.as_view({"get": "list"}), name="perms")
Notice the mapping of request method.

Django - get Month and Year from (Datepicker filter data)

This is forms.py
class dateFilter(forms.Form):
date = forms.DateField(input_formats=['%Y-%M-%D'],label='',required=False,
widget=forms.DateInput(attrs={
"placeholder": " Select a date",
"class": "dateClass",
"id": "datepicker",
"name": "dateFilter"}))
This is my views.py
def bsoCharts(request):
#-----------Filter Section------------------------------------------
dateFilter = forms.dateFilter()
FilteredDate = request.POST.get('date')
return render(request, 'BSO/BSO_dashboard2.html', 'dateFilter':dateFilter )
This is my template where I render the form:
<div class="container">
<div class="row">
<div class="col-12 col-sm-12 col-md-6 col-lg-4">
<form action="{% url 'BSO:BSO_Dashboard' %}" method="post" autocomplete="off">
{% csrf_token %}
{{dateFilter}}
</form>
</div>
</div>
</div>
This is my JQuery for the datepicker:
<script>
$("#datepicker").datepicker( {
changeMonth: true,
changeYear: true,
showButtonPanel: true,
dateFormat: 'yy-m-d',
onSelect: function(dateText, inst) {
$(this).parents("form").submit();
}
});
This is the photo image of the datepicker, and the value after selecting a date:
enter image description here
After getting the results back in my views.py using this get method:
FilteredDate = request.POST.get('date')
i'd like to grab only the month and the year using the below method so I can use it as a filter(contains) to another queryset:
FilteredDate.month
However, it's giving me this error:
Django Version: 2.1.5
Exception Type: AttributeError
Exception Value: 'str' object has no attribute 'month'
Please Help!
request.POST is the raw values dictionary that gets submitted. So the fields are almost always strings (or floats/integers). Never the python values they represent.
When working with Django forms, which you're doing, you should always call form.is_valid() to clean the form and then grab the values from form.cleaned_data:
form = forms.dateFilter(request.POST) # create the form with the data submitted
if form.is_valid(): # check the form is valid (which populates cleaned_data at the same time)
filtered_date = form.cleaned_data.get('date') # this is a python date
if filtered_date: # since date is optional, check it was passed
month = filtered_date.month

Embedding YouTube Videos from User Submitted Comments in Django Webpage

I am making a blog webpage in Django, and I have allowed comments for the blog posts. When a user submits a comment, I am able to use a function in my comment model to identify links they included in their text. I can also embed the video back in the template using an iframe and the list of urls the user had in the comment. The issue that I have is that I am wanting to show the embedded video in the exact same spot the user types in a link. For instance, if the user typed one paragraph of text, and then pasted in a YouTube link, and then they typed one more paragraph, I would want the video to be embedded in between the paragraphs. I have tried several things, but I just haven't been able to figure it out yet. I appreciate any answer I get. Here is the code for the comment model:
class Comment(models.Model):
post = models.ForeignKey('forum.Thread', related_name='comments')
username = models.CharField(max_length=20,default='guest')
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_comment = models.BooleanField(default=False)
def approve(self):
self.approved_comment = True
self.save()
def __str__(self):
return self.text
def get_youtube_urls(self):
urls = []
youtube_regex = (
r'(https?://)?(www\.)?'
'(youtube|youtu|youtube-nocookie)\.(com|be)/'
'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
matches = re.findall(youtube_regex, self.text)
for url in matches:
urls.append(url[5])
return urls
And here is the section of my template that deals with the comments:
<div class="comment">
<strong>Reply from user {{ comment.username }}</strong>
<p>{{ comment.text|linebreaks|urlize }}</p>
{% if comment.get_youtube_urls %}
{% for url in comment.get_youtube_urls %}
<div style="position:relative;height:0;padding-bottom:56.25%"><iframe src="https://www.youtube.com/embed/{{url}}?ecver=2" width="640" height="360" frameborder="0" style="position:absolute;width:50%;height:50%;left:0" allowfullscreen></iframe></div>
{% endfor %}
{% endif %}
<div class="date">{{ comment.created_date }}</div>
</div>
Does any one have any ideas on how to embed video where the user places the link in the text?
Previously, I explained such as below because I created an issue save regex permalink not work well.
So, if you want to test it you can try manually.
I check your regex patterns on http://www.pyregex.com with;
1. mode: findall
2. patterns:
(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})
3. test strings:
https://youtu.be/yVpbFMhOAwE
https://www.youtube.com/watch?v=8Z5EjAmZS1o
https://www.youtube.com/embed/yVpbFMhOAwE
<iframe width="560" height="315" src="https://www.youtube.com/embed/Tlf00NT6mig" frameborder="0" allowfullscreen></iframe>
<iframe width="560" height="315" src="https://www.youtube.com/embed/KQzCxdO3hvo" frameborder="0" allowfullscreen></iframe>
https://www.youtube.com/watch?v=VslLZcV9ZcU&list=RD8Z5EjAmZS1o&index=2
4. result:
["https://","","youtu","be","","yVpbFMhOAwE"]
["https://","www.","youtube","com","watch?v=","8Z5EjAmZS1o"]
["https://","www.","youtube","com","embed/","yVpbFMhOAwE"]
["https://","www.","youtube","com","embed/","Tlf00NT6mig"]
["https://","www.","youtube","com","embed/","KQzCxdO3hvo"]
["https://","www.","youtube","com","watch?v=","VslLZcV9ZcU"]
I think you should do this to get the video id;
def get_youtube_urls(self):
youtube_regex = (
r'(https?://)?(www\.)?'
'(youtube|youtu|youtube-nocookie)\.(com|be)/'
'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
matches = re.findall(youtube_regex, self.text)
urls = []
for url in matches:
id = url[-1] # from last index of `matches` list.
if len(id) == 11: # check if `id` is valid or not.
urls.append(id)
return urls
If it doesn't work, I suggest you to change the value of youtube_regex above with this (without saving in the tuple).
youtube_regex = r'(https?://)?(www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
# OR, recomended to use this;
youtube_regex = r'http(?:s?):\/\/(?:www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
Update
To get the correct position, I suggest you to use finditer;
>>> youtube_regex = r'http(?:s?):\/\/(?:www\.)?(youtube|youtu|youtube-nocookie)\.(com|be)/(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})'
>>> pattern = re.compile(youtube_regex)
>>> [{'origin': m.group(), 'id': m.group(4), 'position': m.start()} for m in pattern.finditer(test_strings) ]
[
{'position': 0, 'id': 'yVpbFMhOAwE', 'origin': 'https://youtu.be/yVpbFMhOAwE'},
{'position': 30, 'id': '8Z5EjAmZS1o', 'origin': 'https://www.youtube.com/watch?v=8Z5EjAmZS1o'},
{'position': 75, 'id': 'yVpbFMhOAwE', 'origin': 'https://www.youtube.com/embed/yVpbFMhOAwE'},
{'position': 156, 'id': 'Tlf00NT6mig', 'origin': 'https://www.youtube.com/embed/Tlf00NT6mig'},
{'position': 280, 'id': 'KQzCxdO3hvo', 'origin': 'https://www.youtube.com/embed/KQzCxdO3hvo'},
{'position': 366, 'id': 'VslLZcV9ZcU', 'origin': 'https://www.youtube.com/watch?v=VslLZcV9ZcU'}
]
>>
And then, handle it with templatetags. this script below isn't completed yet, you need to remove the tag such as <iframe, or else from the origin text comment..
import re
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
def iframe_video(id):
"""
return html string iframe video from the `id`.
"""
return '<iframe src="https://www.youtube.com/embed/{}"></iframe>'.format(id)
#register.filter
def safe_comment(text_comment):
"""
{{ comment.text|safe_comment|linebreaks|urlize }}
"""
youtube_regex = (r'http(?:s?):\/\/(?:www\.)?'
'(youtube|youtu|youtube-nocookie)\.(com|be)/'
'(watch\?v=|embed/|v/|.+\?v=)?([^&=%\?]{11})')
pattern = re.compile(youtube_regex)
matches = [
{'origin': m.group(), 'id': m.group(4), 'position': m.start()}
for m in pattern.finditer(text_comment)
]
for match in matches:
id = match['id']
origin = match['origin']
position = match['position']
# text_comment.replace(origin, iframe_video(id))
# do something to replace tag <iframe, or else..
return mark_safe(text_comment)

Django Braintree : KeyError at /payments-billing/

I am trying to implement django-braintree for my django-oscar project. I installed the django-braintree app and added my details in settings.py. I got a credit card details page(/payment-details). I add /pay url for a simple form. But when I am submitting it KeyError expiration_month at /payments-billing/ is coming. How do I send it to the server ?
My server side:
braintree.Configuration.configure(
braintree.Environment.Sandbox,
"****",
"****",
"****"
)
def client_token():
return braintree.ClientToken.generate()
def create_purchase(request):
nonce = request.form["payment_method_nonce"]
result = braintree.Transaction.sale({
"amount": "1000.00",
"credit_card": {
"number": "4111111111111111",
"expiration_date": "05/2020"
}
})
def pay(request):
try:
resp_dict = {}
resp_dict["client_token"] = client_token()
resp_dict["result"] = result
except Exception as e:
resp_dict["error"] = e
return TemplateResponse(request, 'django_braintree/pay_new.html', resp_dict)
My HTML:
{% block layout %}
<form id="checkout" method="post" action="/payments-billing/">
{% csrf_token %}
<div id="payment-form"></div>
<input type="submit" value="Pay $10">
</form>
{% endblock %}
{% block scripts %}
<script src="https://js.braintreegateway.com/v2/braintree.js"></script>
<script>
braintree.setup(
// Replace this with a client token from your server
"{{ client_token }}",
"dropin", {
container: "payment-form"
});
</script>
{% endblock %}
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact support.
The third-party django-braintree library is not currently compatible with the Braintree Drop-In. The Drop-In only supports expiration_date while the form in the library expects expiration_month and expiration_year, causing the KeyError you're getting. This library has also not been updated to use payment method nonces. I would not recommend using this library, but instead write your own django integration to Braintree using our Python client library.

Ordering admin.ModelAdmin objects

Let's say I have my pizza application with Topping and Pizza classes and they show in Django Admin like this:
PizzaApp
-
Toppings >>>>>>>>>> Add / Change
Pizzas >>>>>>>>>> Add / Change
But I want them like this:
PizzaApp
-
Pizzas >>>>>>>>>> Add / Change
Toppings >>>>>>>>>> Add / Change
How do I configure that in my admin.py?
A workaround that you can try is tweaking your models.py as follows:
class Topping(models.Model):
.
.
.
class Meta:
verbose_name_plural = "2. Toppings"
class Pizza(models.Model):
.
.
.
class Meta:
verbose_name_plural = "1. Pizzas"
Not sure if it is against the django's best practices but it works (tested with django trunk).
Good luck!
PS: sorry if this answer was posted too late but it can help others in future similar situations.
If you want to solve this in 10 seconds just use spaces in verbose_name_plural, for example:
class Topping(models.Model):
class Meta:
verbose_name_plural = " Toppings" # 2 spaces
class Pizza(models.Model):
class Meta:
verbose_name_plural = " Pizzas" # 1 space
Of course it isn't ellegant but may work for a while before we get a better solution.
I eventually managed to do it thanks to this Django snippet, you just need to be aware of the ADMIN_REORDER setting:
ADMIN_REORDER = (
('app1', ('App1Model1', 'App1Model2', 'App1Model3')),
('app2', ('App2Model1', 'App2Model2')),
)
app1 must not be prefixed with the project name, i.e. use app1 instead of mysite.app1.
There's now a nice Django package for that:
https://pypi.python.org/pypi/django-modeladmin-reorder
Answer in June 2018
This answer is similar to Vasil's idea
I tried to solve similar problems, and then I saw such the fragment.
I made some modifications based on this clip. The code is as follows.
# myproject/setting.py
...
# set my ordering list
ADMIN_ORDERING = [
('pizza_app', [
'Pizzas',
'Toppings'
]),
]
# Creating a sort function
def get_app_list(self, request):
app_dict = self._build_app_dict(request)
for app_name, object_list in ADMIN_ORDERING:
app = app_dict[app_name]
app['models'].sort(key=lambda x: object_list.index(x['object_name']))
yield app
# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
...
Note that the sorting list used in this sorting function contains the sorting of all app and its modules in the system. If you don't need it, please design the sorting function according to your own needs.
It works great on Django 2.0
This is actually covered at the very bottom of Writing your first Django app, part 7.
Here's the relevant section:
Customize the admin index page
On a similar note, you might want to
customize the look and feel of the
Django admin index page.
By default, it displays all the apps
in INSTALLED_APPS that have been
registered with the admin application,
in alphabetical order. You may want to
make significant changes to the
layout. After all, the index is
probably the most important page of
the admin, and it should be easy to
use.
The template to customize is
admin/index.html. (Do the same as with
admin/base_site.html in the previous
section -- copy it from the default
directory to your custom template
directory.) Edit the file, and you'll
see it uses a template variable called
app_list. That variable contains every
installed Django app. Instead of using
that, you can hard-code links to
object-specific admin pages in
whatever way you think is best.
Here's the snippet Emmanuel used, updated for Django 1.8:
In templatetags/admin_reorder.py:
from django import template
from django.conf import settings
from collections import OrderedDict
register = template.Library()
# from http://www.djangosnippets.org/snippets/1937/
def register_render_tag(renderer):
"""
Decorator that creates a template tag using the given renderer as the
render function for the template tag node - the render function takes two
arguments - the template context and the tag token
"""
def tag(parser, token):
class TagNode(template.Node):
def render(self, context):
return renderer(context, token)
return TagNode()
for copy_attr in ("__dict__", "__doc__", "__name__"):
setattr(tag, copy_attr, getattr(renderer, copy_attr))
return register.tag(tag)
#register_render_tag
def admin_reorder(context, token):
"""
Called in admin/base_site.html template override and applies custom ordering
of apps/models defined by settings.ADMIN_REORDER
"""
# sort key function - use index of item in order if exists, otherwise item
sort = lambda order, item: (order.index(item), "") if item in order else (
len(order), item)
if "app_list" in context:
# sort the app list
order = OrderedDict(settings.ADMIN_REORDER)
context["app_list"].sort(key=lambda app: sort(order.keys(),
app["app_url"].strip("/").split("/")[-1]))
for i, app in enumerate(context["app_list"]):
# sort the model list for each app
app_name = app["app_url"].strip("/").split("/")[-1]
if not app_name:
app_name = app["name"].lower()
model_order = [m.lower() for m in order.get(app_name, [])]
context["app_list"][i]["models"].sort(key=lambda model:
sort(model_order, model["admin_url"].strip("/").split("/")[-1]))
return ""
In settings.py:
ADMIN_REORDER = (
('app1', ('App1Model1', 'App1Model2', 'App1Model3')),
('app2', ('App2Model1', 'App2Model2')),
)
(insert your own app names in here. Admin will place missing apps or models at the end of the list, so long as you list at least two models in each app.)
In your copy of base_site.html:
{% extends "admin/base.html" %}
{% load i18n admin_reorder %}
{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}
{% block branding %}
{% admin_reorder %}
<h1 id="site-name">{% trans 'Django administration' %}</h1>
{% endblock %}
{% block nav-global %}{% endblock %}
ADMIN_ORDERING = {
"PizzaApp": [
"Pizzas",
"Toppings",
],
}
def get_app_list(self, request):
app_dict = self._build_app_dict(request)
for app_name, object_list in app_dict.items():
if app_name in ADMIN_ORDERING:
app = app_dict[app_name]
app["models"].sort(
key=lambda x: ADMIN_ORDERING[app_name].index(x["object_name"])
)
app_dict[app_name]
yield app
else:
yield app_dict[app_name]
admin.AdminSite.get_app_list = get_app_list
This solution works for me, modified the one from ζž—δΌŸι›„.
You get to keep the default auth ordering AND specify your own.
If you're using Suit for the AdminSite you can do menu customization using the menu tag.
I was looking for a simple solution where I could order the apps by their name in the admin panel. I came up with the following template tag:
from django import template
from django.conf import settings
register = template.Library()
#register.filter
def sort_apps(apps):
apps.sort(
key = lambda x:
settings.APP_ORDER.index(x['app_label'])
if x['app_label'] in settings.APP_ORDER
else len(apps)
)
print [x['app_label'] for x in apps]
return apps
Then, just override templates/admin/index.html and add that template tag:
{% extends "admin/index.html" %}
{% block content %}
{% load i18n static sort_apps %}
<div id="content-main">
{% if app_list %}
{% for app in app_list|sort_apps %}
<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 %}
<td>{% trans 'Change' %}</td>
{% else %}
<td> </td>
{% endif %}
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
{% else %}
<p>{% trans "You don't have permission to edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
Then customized the APP_ORDER in settings.py:
APP_ORDER = [
'app1',
'app2',
# and so on...
]
It works great on Django 1.10
Here's a version that gives you a bit more flexibility, namely:
You can partially define apps ordering, leaving the rest for Django to add to the list
You can specify order on modules, or avoid defining it, by using '*' instead
Your defined apps ordering will appear first, then all the rest of apps appended after it
To check the name of your app, either look at the file apps.py inside the app's directory and check for name property of the class Config(AppConfi): or in case that is not present, use the name of the directory for the app in the project.
Add this code somewhere in your settings.py file:
# ======[Setting the order in which the apps/modules show up listed on Admin]========
# set my ordering list
ADMIN_ORDERING = [
('crm', '*'),
('property', '*'),
]
# Creating a sort function
def get_app_list(self, request):
"""
Returns a sorted list of all the installed apps that have been
registered in this site.
Allows for:
ADMIN_ORDERING = [
('app_1', [
'module_1',
'module_2'
]),
('app_2', '*'),
]
"""
app_dict = self._build_app_dict(request)
# Let's start by sorting the apps alphabetically on a list:
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sorting the models alphabetically within each app.
for app in app_list:
if app['app_label'] in [el[0] for el in ADMIN_ORDERING]:
app_list.remove(app)
else:
app['models'].sort(key=lambda x: x['name'])
# Now we order the app list in our defined way in ADMIN_ORDERING (which could be a subset of all apps).
my_ordered_apps = []
if app_dict:
for app_name, object_list in ADMIN_ORDERING:
app = app_dict[app_name]
if object_list == '*':
app['models'].sort(key=lambda x: x['name'])
else:
app['models'].sort(key=lambda x: object_list.index(x['object_name']))
my_ordered_apps.append(app)
# Now we combine and arrange the 2 lists together
my_ordered_apps.extend(app_list)
return my_ordered_apps
# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
# =========================================
This is nothing more than overwriting the function defined by Django on the file python2.7/site-packages/django/contrib/admin/sites.py.
That get_app_list method of class AdminSite(object): produces a data structure with all apps on the project, including for Django's auth app, such as:
[
{
"app_label": "auth",
"app_url": "/admin/auth/",
"has_module_perms": "True",
"models": [
{
"add_url": "/admin/auth/group/add/",
"admin_url": "/admin/auth/group/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f990>",
"object_name": "Group",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
},
{
"add_url": "/admin/auth/user/add/",
"admin_url": "/admin/auth/user/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f710>",
"object_name": "User",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
}
],
"name": "<django.utils.functional.__proxy__ object at 0x108b81850>"
},
{
"app_label": "reservations",
"app_url": "/admin/reservations/",
"has_module_perms": "True",
"models": [
{
"add_url": "/admin/reservations/reservationrule/add/",
"admin_url": "/admin/reservations/reservationrule/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f6d0>",
"object_name": "ReservationRule",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
}
],
"name": "Availability"
},
{
"app_label": "blog",
"app_url": "/admin/blog/",
"has_module_perms": "True",
"models": [
{
"add_url": "/admin/blog/category/add/",
"admin_url": "/admin/blog/category/",
"name": "Categories",
"object_name": "Category",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
},
{
"add_url": "/admin/blog/post/add/",
"admin_url": "/admin/blog/post/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f110>",
"object_name": "Post",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
},
{
"add_url": "/admin/blog/tag/add/",
"admin_url": "/admin/blog/tag/",
"name": "<django.utils.functional.__proxy__ object at 0x11057f390>",
"object_name": "Tag",
"perms": {
"add": "True",
"change": "True",
"delete": "True"
}
}
],
"name": "Blog"
},
(...)
]
This is just a wild stab in the dark, but is there any chance that the order in which you call admin.site.register(< Model class >, < ModelAdmin class >) can determine the display order? Actually, I doubt that would work because I believe Django maintains a registry of the Model -> ModelAdmin objects implemented as a standard Python dictionary, which does not maintain iteration ordering.
If that doesn't behave the way you want, you can always play around with the source in django/contrib/admin. If you need the iteration order maintained, you could replace the _registry object in the AdminSite class (in admin/sites.py) with a UserDict or DictMixin that maintains insertion order for the keys. (But please take this advice with a grain of salt, since I've never made these kinds of changes myself and I'm only making an educated guess at how Django iterates over the collection of ModelAdmin objects. I do think that django/contrib/admin/sites.py is the place to look for this code, though, and the AdminSite class and register() and index() methods in particular are what you want.)
Obviously the nicest thing here would be a simple option for you to specify in your own /admin.py module. I'm sure that's the kind of answer you were hoping to receive. I'm not sure if those options exist, though.
My solution was to make subclasses of django.contrib.admin.sites.AdminSite and django.contrib.admin.options.ModelAdmin .
I did this so I could display a more descriptive title for each app and order the appearance of models in each app. So I have a dict in my settings.py that maps app_labels to descriptive names and the order in which they should appear, the models are ordered by an ordinal field I provide in each ModelAdmin when I register them with the admin site.
Although making your own subclasses of AdminSite and ModelAdmin is encouraged in the docs, my solution looks like an ugly hack in the end.
Copy lib\site-packages\django\contrib\admin\templates\admin\index.html template to project1\templates\admin\ directory, where project1 is the name of your project.
In the copied file, i.e. project1\templates\admin\index.html, replace lines:
{% block content %}
...
{% endblock %}
with:
{% block content %}
<div id="content-main">
{% if app_list %}
<div class="module">
<table>
<caption>App 1</caption>
<tr> <th> Model 1 </th> <td>Description of model 1</td> </tr>
<tr> <th> Model 2 </th> <td>Description of model 1</td> </tr>
<tr> <th> <a href="..." >...</a> </th> <td>...</td> </tr>
</table>
</div>
<div class="module">
<table>
<caption>Authentication and authorization</caption>
<tr> <th> <a href="/admin/auth/user/" >Users</a> </th> <td>List of users</td> </tr>
<tr> <th> <a href="/admin/auth/group/" >Groups</a> </th> <td>List of users' groups</td> </tr>
</table>
</div>
{% else %}
<p>{% trans "You don't have permission to view or edit anything." %}</p>
{% endif %}
</div>
{% endblock %}
where:
app1 is the name of your application with models,
modeli is the name of i-th model in app1.
If you defined more than one application with models in your project, then simply add another table in the above index.html file.
Because we change the template, we can freely change its HTML code. For example we can add a description of the models as it was shown above. You can also restore Add and Change links - I deleted them since I think they are redundant.
The answer is a practical demonstration of the solution from Dave Kasper's answer.
custom_admin.py
from django.contrib.admin import AdminSite
class CustomAdminSite(AdminSite):
def get_urls(self):
urls = super(MyAdminSite, self).get_urls()
return urls
def get_app_list(self, request):
app_dict = self._build_app_dict(request)
ordered_app_list = []
if app_dict:
# TODO: change this dict
admin_ordering = {
'app1': ('Model1', 'Model2'),
'app2': ('Model7', 'Model4'),
'app3': ('Model3',),
}
ordered_app_list = []
for app_key in admin_ordering:
app = app_dict[app_key]
app_ordered_models = []
for model_name in admin_ordering[app_key]:
for model in app_dict[app_key]['models']:
if model['object_name'] == model_name:
app_ordered_models.append(model)
break
app['models'] = app_ordered_models
ordered_app_list.append(app)
return ordered_app_list
admin_site = CustomAdminSite()
urls.py
from custom_admin import admin_site
urlpatterns = [
path('admin/', admin_site.urls),
]
Add the below code to your settings.py:
def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered on this site.
"""
ordering = {
# for superuser
'Group': 1,
'User': 2,
# fist app
'TopMessage': 101,
'Slider': 102,
'ProductCategory': 103,
'Product': 104,
'ProductPicture': 105,
# 2nd app
'ProductReview': 201,
'Promotion': 202,
'BestSeller': 203,
}
app_dict = self._build_app_dict(request)
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
for app in app_list:
app['models'].sort(key=lambda x: ordering[x['object_name']])
return app_list
admin.AdminSite.get_app_list = get_app_list
And make changes to the ordering dictionary to match your apps and models. That's it.
The benefit of my solution is that it will show the 'auth' models if the user is a superuser.
Django, by default, orders the models in admin alphabetically. So the order of models in Event admin is Epic, EventHero, EventVillain, Event
Instead you want the order to be
EventHero, EventVillain, Epic then event.
The template used to render the admin index page is admin/index.html and the view function is ModelAdmin.index.
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)
The method get_app_list, set the order of the models.:
def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
app_dict = self._build_app_dict(request)
# Sort the apps alphabetically.
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(key=lambda x: x['name'])
return app_list
So to set the order we override get_app_list as:
class EventAdminSite(AdminSite):
def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
ordering = {
"Event heros": 1,
"Event villains": 2,
"Epics": 3,
"Events": 4
}
app_dict = self._build_app_dict(request)
# a.sort(key=lambda x: b.index(x[0]))
# Sort the apps alphabetically.
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sort the models alphabetically within each app.
for app in app_list:
app['models'].sort(key=lambda x: ordering[x['name']])
return app_list
The code app['models'].sort(key=lambda x: ordering[x['name']]) sets the fixed ordering. Your app now looks like this.
Check the Documentation
In Settings.py file setting the order in which the apps/modules show up listed on Admin
set my ordering list
ADMIN_ORDERING = [
('Your_App1', '*'),
('Your_App2', '*'),
]
Creating a sort function below ADMIN_ORDERING
def get_app_list(self, request):
"""
You Can Set Manually Ordering For Your Apps And Models
ADMIN_ORDERING = [
('Your_App1', [
'module_1',
'module_2'
]),
('Your_App2', '*'),
]
"""
app_dict = self._build_app_dict(request)
# Let's start by sorting the apps alphabetically on a list:
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
# Sorting the models alphabetically within each app.
for app in app_list:
if app['app_label'] in [el[0] for el in ADMIN_ORDERING]:
app_list.remove(app)
else:
app['models'].sort(key=lambda x: x['name'])
# Now we order the app list in our defined way in ADMIN_ORDERING (which could be a subset of all apps).
my_ordered_apps = []
if app_dict:
for app_name, object_list in ADMIN_ORDERING:
app = app_dict[app_name]
if object_list == '*':
app['models'].sort(key=lambda x: x['name'])
else:
app['models'].sort(key=lambda x: object_list.index(x['object_name']))
my_ordered_apps.append(app)
# Now we combine and arrange the 2 lists together
my_ordered_apps.extend(app_list)
return my_ordered_apps
# Covering django.contrib.admin.AdminSite.get_app_list
from django.contrib import admin
admin.AdminSite.get_app_list = get_app_list
Quite a simple and self contained approach can be using the decorator pattern to resort your app and modules in this way:
# admin.py
from django.contrib import admin
def app_resort(func):
def inner(*args, **kwargs):
app_list = func(*args, **kwargs)
# Useful to discover your app and module list:
#import pprint
#pprint.pprint(app_list)
app_sort_key = 'name'
app_ordering = {
"APP_NAME1": 1,
"APP_NAME2": 2,
"APP_NAME3": 3,
}
resorted_app_list = sorted(app_list, key=lambda x: app_ordering[x[app_sort_key]] if x[app_sort_key] in app_ordering else 1000)
model_sort_key = 'object_name'
model_ordering = {
"Model1": 1,
"Model2": 2,
"Model3": 3,
"Model14": 4,
}
for app in resorted_app_list:
app['models'].sort(key=lambda x: model_ordering[x[model_sort_key]] if x[model_sort_key] in model_ordering else 1000)
return resorted_app_list
return inner
admin.site.get_app_list = app_resort(admin.site.get_app_list)
This code sorts at the top only the defined keys in the ordering dict, leaving at the bottom all the other.
The approach is clean but it requires get_app_list to be executed before... which is probably not a good idea when performance is important and you have a large app_list.
This has become a lot easier in Django 4.1:
The AdminSite.get_app_list() method now allows changing the order of apps and models on the admin index page.
You can subclass and override this method to change the order the returned list of apps/models.