Enclosing an Admin class within a Model? - django

I've often seen examples like this in other projects' code:
class Modela(models.Model):
abbr = models.CharField()
name = models.TextField()
class Admin:
list_display = ('abbr', 'name',)
Unfortunately, when I do this in my own code and then register the model with:
admin.site.register(Modela)
...the list_display variable is ignored and I get the output of __str__ for each entry in the database.
Do I really need to write separate ModelAdmin classes for each class, or is there a way to do it as above?

This syntax is from a very old version of Django - pre-'newforms-admin', which merged before 1.0 was released over three years ago. It doesn't work any more, and there's absolutely no reason to use it.
If you just want to change the list_display, though, there isn't any need to create classes: you can pass that into the register call:
admin.site.register(Modela, list_display=('abbr', 'name'))

Related

Accessing the "many" side from the "one" side effectively

Considering the following models:
class ManySide(django.db.models.Model):
one_side = models.ForeignKey(
to=OneSide, on_delete=models.PROTECT, related_name="related_one_side"
)
class OneSide(django.db.models:model):
# not containing any field relevant here
def many_side_elements(self):
pass # ?
What should I include in method many_side_elements so that calling it from a OneSide Model instance would list a queryset of ManySide elements?
Official docs imply that given o is a OneSide isntance, o.many_side_set.all() should work but it returns an error in shell.
My current solution is the following:
from django.apps import apps
[...]
def many_side_elements(self):
ManySideModel = apps.get_model('<app_name_here>', 'ManySide')
val = ManySideModel.objects.filter(one_side=self)
But I'm concerned it's ineffective since it requires importing the other Model. Actually it caused a circular dependency error message in my project, hence the get_model usage.
Is there any better way? Or xy_set should work in the first place? Then what am I doing wrong?
If you create the model field with a related name, the related name overrides the _set queryset.
In your case
o.related_one_side.all() should work without the need for another def.
See: https://docs.djangoproject.com/en/4.0/topics/db/queries/#following-relationships-backward
You can override the FOO_set name by setting the related_name parameter in the ForeignKey definition. For example, if the Entry model was altered to blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries'), the above example code would look like this: b.entries.all()

Django admin shows models name with extra "s" at the end

I'm new to Django. In the admin panel, the name of the models has an extra "s" at the end. How can I fix that?
Why is django-admin adding an 's'
There is a naming convention in Django the models are named in the singular. So your classes should be named:
class Comment(models.Model):
...
class Note(models.Model):
...
An instance of such a class represents a single comment, not a set of comments. Likewise consider the following
# doesn't really make sense
Comments.objects.get(...)
# reads much better
Comment.objects.get(...) #e.g. get from the comment objects
This is so much of a convention that Django admin expects for your classes to be named in the singular. For this reason, Django adds on an 's' as a standard way of pluralising your class names. (This will admittedly not work for all words and is very anglo-centric, but as an override, Django provides the verbose_name_plural meta-attribute).
How to fix it:
Rename your classes so they are in the singular. This will make your code a lot more readable to anyone who is used to reading django. It will also make your models named in a way consistent with other third-party packages. If your class names are not in English, or do not pluralise simply by appending an s, use verbose_name_plural e.g:
Class Cactus(models.Model):
class Meta:
verbose_name_plural = 'Cacti'
Add a meta class to your model and set verbose_name_plural (https://docs.djangoproject.com/en/3.2/ref/models/options/#verbose-name-plural). Ideally name your classes as singular (in this case you'd have class Comment(models.Model).
class Comments(models.Model):
class Meta:
verbose_name_plural = 'Comments'
Good read:
https://simpleisbetterthancomplex.com/tips/2018/02/10/django-tip-22-designing-better-models.html#:~:text=Always%20name%20your%20models%20using,not%20a%20collection%20of%20companies.

How to display manytomany field in django admin with a related model in a user-friendly way?

I'm struggling to display a manytomany field in the admin with the related model in a user-friendly manner. The project is already up and running so adding a through table is not preferred.
The set-up is something along these lines;
class User(AbstractUser):
is_member_of_organization = models.ManyToManyField(Organization, blank=True, verbose_name=_("is member of organization(s)"), related_name='orgmembers')
class Organization(models.Model):
name = models.CharField(max_length=60, verbose_name=_("organization name"))
the only reasonable way I manage to display the related users with organization admin is via a TabularInline
admin.py
class UserOrgAdminInLine(admin.TabularInline):
model = User.is_admin_for_organization.through
extra = 0
#admin.register(Organization)
class OrganizationAdmin(admin.ModelAdmin):
inlines = (UserOrgAdminInLine,)
However, looking up users is not convenient as soon as their number increases. I would like something similar to filer_horizontal but I am not sure how to include it directly in the OrganizationAdmin admin class. Furthermore, I am using fieldsets (which I believe should have no special rules or syntax to it compared to ordinary fields = .
One little subquestion - in the tabular inline, when I use only model = User django throws an error that there is no foreign key to it, but when I use the additional .is_admin_for_organization.through it works, but there is no through table and I though that this work just in that case. Why is that?
Any help would be much appreciated.
Try adding
class UserOrgAdminInLine
raw_id_fields = ("is_member_of_organization",)

How can I use a different object manager for related models?

I have a couple of classes:
class Person(models.Model):
address = models.ForeignKey(Address, on_delete=models.CASCADE)
class Address(LiveModel):
address = models.CharField(max_length=512)
some_manager = SomeManager()
some_other_object_manager = OtherManager()
class Meta:
base_manager_name = 'some_other_object_manager'
Because I set some_manager, the default manager used is SomeManager which is good. BUT if I am querying a Person, I want it to use Address's OtherManager manager for querying and I thought that by setting base_manager_name, I would be able to achieve this (https://docs.djangoproject.com/en/2.2/topics/db/managers/#using-managers-for-related-object-access). Unfortunately this does not work. Any ideas? Particularly I am trying to achieve this in the admin, if that makes a difference.
To clarify, this does work as intended. The issue here was in the detail. I was using the Django admin which does not query the related fields the way I expected. It actually uses the related fields default manager for the queryset. If you want to do what I am trying to do, this is a nice simple example: https://books.agiliq.com/projects/django-admin-cookbook/en/latest/filter_fk_dropdown.html

How to reference model joined across foreign key in django admin

I have a django 1.6 app with the following (trimmed for clarity)
classes defined. User is the standard django.contrib.auth User class.
class Event(models.Model):
user = models.ForeignKey(User, related_name='events')
name = models.CharField(max_length=64)
class Profile(models.Model):
user = models.ForeignKey(User, related_name='aprofile')
class MemberProfile(Profile):
pass
Here are my admin classes:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', )
class MemberProfileAdmin(ModelAdmin):
model = MemberProfile
fields = ('user', )
readonly_fields = ('user', )
What I'd like to do is display a read-only list of all events for a given member, or at least profile. Of course joining across the User foreign key seems like the way to go, but I am drawing a blank as to how to accomplish this. Here's a summary of attempts so far.
Define an inline admin on the Event class directly referencing the user field, and add it to the ProfileAdmin:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user' # Fails - fk_name 'user' is not a ForeignKey to <class 'solo365.solo_profile.models.profile.Profile'>
...well, no, it sure isn't. But our User has an 'aprofile' field, so...
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile' # Fails - EventInlineAdmin.fk_name' refers to field 'user__aprofile' that is missing from model 'admin_fk_test.Event'.
Ok, those fields look like they should sync up, but perhaps we need to be a little more aggressive:
class EventInlineAdmin(TabularInline):
model = Event
fk_name = 'user__aprofile__pk' # Fails - 'EventInlineAdmin.fk_name' refers to field 'user__aprofile__pk' that is missing from model 'admin_fk_test.Event'.
I've also tried messing with formfield_for_foreignkey() and friends in both the inline and the regular model admins, but without that fk_name having a valid value, those methods never get called.
I then considered trying to access the events field directly from a Profile's user:
class ProfileAdmin(ModelAdmin):
model = Profile
fields = ('user', 'user__events') # Fails - Unknown field(s) (user__events) specified for Profile. Check fields/fieldsets/exclude attributes of class ProfileAdmin.
What about with a custom formfield_for_foreignkey() method? Sadly that never gets called for anything other than the 'user' field. I've also considered a custom get_formsets() method, but frankly I'm not sure how I could use that without a working EventInlineAdmin.
I could of course define a custom field that simply concatenates all of the events and returns that as a string, but ideally I would prefer something like a fully-featured inline (even read-only) than just a chunk o' text. IOW such a custom field would have a method that (ideally) would return an inline form without requiring any sort of custom template, setting of allow_tags, etc.
Am I doomed to have to create a completely custom Form for the Profile admin class? Or is there a simple way to accomplish what I'm trying to do, that I'm just missing?
Update:
Bonus points if a provided solution works for the MemberProfileAdmin class, not just the ProfileAdmin class.
The relation between User and Profile should be a 1:1 relation which would allow the referencing via user__aprofile. Otherwise, the reverse relation of a foreing key is a queryset because one foreign key can be assigned to multiple instances. This is might be the reason why your code failed.
Change it to:
class Profile(models.Model):
user = models.OneToOneKey(User, related_name='aprofile')
This is a bit like using ForeignKey(unique=True).
To know the attributes, it might help to call dir(model_instance) on the model instance in question, or try around in the Django shell (./manage.py shell).
Also, I've experienced that it might be more confusing to assign a custom related_name like in your case where you would expect one profile by looking at the related name but you would actually get back a queryset.
The generated name in that case would be profile_set, and you would have to call profile_set.all() or profile_set.values() to get some actual profiles.