i am learning Django right now and still struggling with it. I have two models: Database and DatabaseUser, linked with a foreign-key. In the admin page, I want to add a link to the list with databases, that directs me to a list with database-users of that specific database.
Python: 3.9; Django: 4.2.13
# models.py
class Database(models.Model):
datenbank_name = models.CharField(max_length=300)
datenbank_art = models.CharField(max_length=100)
# ... (omitted some attributes)
def __str__(self):
return self.datenbank_name
class DatabaseUser(models.Model):
datenbank = models.ForeignKey(Database, on_delete=models.CASCADE)
user_name = models.CharField(max_length=300)
# ... (omitted some attributes)
def __str__(self):
return self.user_name
My admin models are looking like this:
# admin.py
class PasswordInput(forms.ModelForm):
passwort = forms.CharField(widget=forms.PasswordInput())
class DatabaseAdmin(admin.ModelAdmin):
form = PasswordInput
fieldsets = [
("Allgemein", {"fields": ["datenbank_name", "datenbank_art", "kunde", "host", "port"]}),
("Anmeldeinformationen", {"fields": ["user_name", "passwort"]}),
]
list_display = ["datenbank_name", "kunde", "datenbank_art", "port", "erreichbar"]
admin.site.register(Database, DatabaseAdmin)
class DatabaseUserAdmin(admin.ModelAdmin):
form = PasswordInput
fieldsets = [
("Datenbank", {"fields": ["datenbank"]}),
("Anmeldeinformationen", {"fields": ["user_name", "passwort"]}),
("Datenbank-Rechte", {"fields": ["rechte"]}),
]
list_filter = ["datenbank"]
list_display = ["user_name", "rechte", "datenbank"]
admin.site.register(DatabaseUser, DatabaseUserAdmin)
Now I have tried couple of things and found 2 very similar questions on Stack Overflow. However i couldn’t get them to work in my code. The second one is not creating a link to the list of objects, but to the change-view (as far as I’ve understood).
- How can I list all foreign key related objects in Django admin panel?
- Link in django admin to foreign key object
From the first thread (How can I list all foreign key related objects in Django admin panel?) I have tried all three solutions. Unfortunately without success:
My attempt for first solution:
def users(self):
return '<a href="/admin/db_manager/databaseuser/?datenbank__id__exact=%d">%s</a>' % (
self.datenbank_id, self.datenbank)
users.allow_tags = True
list_display = ["datenbank_name", "users", "kunde", "datenbank_art", "port", "erreichbar"]
Errors:
-
In my code
self.datenbank_id
andself.datenbank
are beeing highlighted by PyCharm, saying “Unresolved attribute reference ‘datenbank_id’ for class ‘DatabaseAdmin'” -
Furthermore when I try to open the admin site, it says “users() takes 1 positional argument but 2 were given”
If I add obj to the function: def users(self, obj):
The error is either “‘DatabaseAdmin’ object has no attribute ‘datenbank_id'” (if self.datenbank_id
is used) or ‘Database’ object has no attribute ‘datenbank_id’ (if obj.datenbank_id
is used)
Just can’t figure it out.
My attempt for the second solution:
# models.py
def get_admin_url(self):
return "/admin/db_manager/databaseuser/%d/" % self.id
# admin.py
def databases(self, obj):
html = ""
for obj in DatabaseUser.objects.filter(datenbank__id_exact=self.id):
html += '<p><a href="%s">%s</a></p>' % (obj.get_admin_url(), obj.title)
return html
databases.allow_tags = True
list_display = ["user_name", "rechte", "datenbank", "databases"]
Errors:
- PyCharm highlights
self.id
objects inDatabaseUser.objects.filter
andself.id
in the get_admin_url() function. - Similar other errors than with the first solution. E.g.: adding
obj
to function parameters
I have then tried a solution of the second link (Link in django admin to foreign key object) and: Surprise. I got something that at least loads:
def users(self, obj:DatabaseUser):
link = reverse("admin:db_manager_databaseuser_change", args=[obj.pk])
return mark_safe(f'<a href="{link}">{escape(obj.__str__())}</a>')
users.allow_tags = True
list_display = ["datenbank_name", "users", "kunde", "datenbank_art", "port", "erreichbar"]
Problem is, that the second thread about a slightly different problem. In the list view of the database a link is shown, as I wanted it, in an own column, next to each database.
However this link directs me to the change form, where I can make changes to the attributes of a specific user. Not the list as I wanted it.
I tried to change link = reverse("admin:db_manager_databaseuser_change", args=[obj.pk])
to link = reverse("admin:db_manager_databaseuser_changelist", args=[obj.pk])
. But this will throw me an error:
Reverse for 'db_manager_databaseuser_changelist' with arguments '(2,)' not found. 1 pattern(s) tried: ['admin/db_manager/databaseuser/\Z']
I think one of the main problems for me here is, that I have started with Django like 1 Week ago and until now I can’t get my head around, how everything is connected.
I am a bit desperate right now. I would really appreciate any help, to get this fixed.
Greetings.
carl_3000 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.
1
From what I gather, you want to display all Database
objects, with a column where a link would redirect to all DatabaseUsers
objects referring to the selected Database
. In Django admin, the view where you can see a table of all instances of a model is called the changelist view. So, translated in django admin grammar, what you want is a link in the Database
changelist to the DatabaseUser
changelist, filtered for the relevant Database
.
Now, you want a link, so let’s have a look at how URLs are built. The changelist URL for any model yourmodel
in the app yourapp
is /admin/yourapp/yourmodel/. Filtering happens in the URL too, in Django syntax: /admin/yourapp/yourmodel/?my_field__gte=5 yields all yourmodel
instances with a myfield
value over or equal to 5.
You were nearly there with your first solution. As your errors point out, neither Database
nor DatabaseAdmin
have a datenbank_id
attribute. However, your Database
model has an (implicitly created) id
attribute, so try:
from django.utils.safestring import mark_safe
def users(self, obj):
return mark_safe(
'<a href="/admin/db_manager/databaseuser/?datenbank__id__exact=%d">
See list of users
</a>' % (obj.id))
The mark_safe
is there to ensure that the result is interpreted as HTML and not as text (try without, to see the difference).
From what I understand, your (corrected) second solution would give you a list of links to each relevant DatabaseUser
, instead of a unique link to the list of all relevant DatabaseUsers
. I don’t think it’s what you want.
Third solution: reverse("admin:db_manager_databaseuser_changelist")
would give you the correct link to the DatabaseUser
changelist, but I don’t think you can provide the filter this way (args is something else). You could still use it to rewrite the previous solution:
from django.utils.safestring import mark_safe
from django.urls import reverse
def users(self, obj):
link = reverse("admin:db_manager_databaseuser_changelist")
return mark_safe(
'<a href="' + link + '?datenbank__id__exact=%d">
See list of users
</a>' % (obj.id))
Hope this helps.