We have some models that are have a user as a foreign key. But with about 25000 users in our system, it's a bit daunting to find the one we need.
Is there a solution out there that's better than the select box? Maybe an autocomplete so we can start typing the user name / address? Or just a search box? When switching the related user for these objects, it's getting harder and harder with 25000 unsorted users.
Even just setting it to sort the users by username would be helpful.
I had this problem and my conclusion was to use an autocomplete field instead. It works pretty well in most cases. The only problem is when you have a lot of entries that are mostly the same. For example, in your case, if you type Robert for the name and there's a few hundred Robert entries in the list...
UPDATE
As mentions in shuckc's answer, Django 2.0+ admin as now autocomplete built in.
For older Django or to use outside of the admin (old answer)
There are many apps that add autocomplete to the Django admin:
django-autocomplete-light
django-extensions (ForeignKeyAutocompleteAdmin)
django-autocomplete (on google code)
django-ajax-selects
django-admin-autocomplete
django-autocomplete (tyrion)
My preferred one is the last one. It's well written, it can be used with the admin and outside of the admin, it works with ManyToManyFields, ForeignKeyFields, CharFields, etc.
I did a fork of this project for my client that adds some niceties like a lookup (loupe) button like the ForeignKeyRawIdWidget.
Django 2.0 admin has autocomplete built in, just set the autocomplete_fields field on the ModelAdmin class. e.g.
class QuestionAdmin(admin.ModelAdmin):
ordering = ['date_created']
search_fields = ['question_text']
class ChoiceAdmin(admin.ModelAdmin):
autocomplete_fields = ['question']
The simplest out-of-the-box solution is to add the field to your ModelAdmin's raw_id_fields -- then you'll get a pop-up window in which you can use the built-in searching/filtering and pagination control's to find and select the object you're after.
If you really want autocomplete, the other answers give a you reasonable starting point.
You can use the ForeignKeyRawIdWidget from django.contrib.admin.widgets. It renders FK relations as an input with a small button along-side which presents a searchable pop up.
There is an app for that (django-autocomplete).
Related
my app has grown so that that drop downs in the django admin have 100,000s of options. I can't even open my admin anymore because of its load on the database, not too mention I wouldn't be able to find an option in the select box.
Does django have an autocomplete option?
What is the best option for handling big data in django admin?
Django 2.0 has introduced a new feature on the admin site, called autocomplete_fields, which is helpful for models with foreign keys. It replaces the normal <select> element with an autocomplete element:
class QuestionAdmin(admin.ModelAdmin):
ordering = ['date_created']
search_fields = ['question_text']
class ChoiceAdmin(admin.ModelAdmin):
autocomplete_fields = ['question']
Here's a screenshot of the element:
Use raw_id_fields, like this:
class SomeModelAdmin(admin.ModelAdmin):
raw_id_fields = ('problematic_field', )
You can read more about this, here:
https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields
Basically - it will change the select boxes with raw id field with a lens icon - which open another window to make changes.
There is many solutions to your problem out there but here is two ways I consider as viable:
If you need a built-in solution where you don't want to involve an external library, raw_id_fields may help you. This is from the documentation:
By default, Django’s admin uses a select-box interface () for
fields that are ForeignKey. Sometimes you don’t want to incur the
overhead of having to select all the related instances to display in
the drop-down.
raw_id_fields is a list of fields you would like to change into an
Input widget for either a ForeignKey or ManyToManyField:
class ArticleAdmin(admin.ModelAdmin):
raw_id_fields = ("newspaper",)
Use an auto-complete widget. There is ton of existing packages, see this list. Be careful to look which one has recent commits, it will give you an idea of the community awarness.
I know I should put up some code with what I've tried but I don't know where to start.
In Django admin I can display data so that it can be sorted by the user using:
class MyModelAdmin(admin.ModelAdmin):
list_display = ['field1', 'field2']
ordering = ['field2','field1']
Within the admin site, users will be able to click the top of the column and reorder on that column.
I want to replicate this functionality in an non-admin screen. How do I do that?
You have two approaches available:
Import and modify the admin templates to use their sortable functionality. This answer covers the basics very well., but the ModelAdmin class has a lot of functionality, which you may or may not actually need. You can start from the admin templates more generally if you want to go down that route.
Use an external library to manage in the templates.
I find the latter approach is actually easier, faster and more easily extensible. I have just implemented this in a project using datatables, which takes a couple of minutes, assuming you already have a table.
This scenario happens way too often in my project:
someone adds a model Foo that has several ForeignKey fields, one of them to refers to model Bar
an admin is added for that model (and works OK)
the code is deployed
on production server, Bar has millions of instances
someone accesses Foo's admin page; Django tries to retrieve all Bars at once (to display them in a combo box) and the server gets overloaded
later the problem gets fixed by editing Foo's admin and adding bar to raw_id_fields.
I'd like to prevent this situation from happening in the future, preferably by somehow stating (once and for all) that Bar has many rows and it should be always treated as if the field referring to it was listed in raw_id_fields in all admin pages. Is this possible somehow?
This is an excelent point. This is a critical issue that can turn down the database and even the web server.
Considering this, I believe the default approach MUST be the raw_id_fields thing. If you know what you are doing, then you change this behavior.
Unfortunately, most of the authors of the admin interfaces libraries disagree of this thought. Not just for Python-Django, but also for other communities like Ruby-Rails.
5 years ago I got tired having the same problem, then I developed the django-smart-autoregister, that do this and also auto configure using another good patterns. Even today I face this problem, so I guess it is worth to take a look.
ps: the library was originally implemented using a modular approach, though you just call some functions that will configure the raw_id_fields for you according to the model field.
From the docs:
ForeignKey is represented by django.forms.ModelChoiceField, which is a
ChoiceField whose choices are a model QuerySet.
ModelChoiceField extends Field, and therefor has a widget property that can be abused
https://github.com/django/django/blob/master/django/forms/fields.py#L49
Add this somewhere in your project files.
from django.forms import ModelChoiceField
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
ModelChoiceField.widget = ForeignKeyRawIdWidget
downside: this is also going to happen on non-admin forms
With Django 1.5 and the introduction of custom user models the AUTH_PROFILE_MODULE became deprecated. In my existing Django application I use the User model and I also have a Profile model with a foreign key to the User and store other stuff about the user in the profile. Currently using AUTH_PROFILE_MODULE and this is set to 'app.profile'.
So obviously, my code tends to do lots of user.get_profile() and this now needs to go away.
Now, I could create a new custom user model (by just having my profile model extend User) but then in all other places where I currently have a foreign key to a user will need to be changed also... so this would be a large migration in my live service.
Is there any way - and with no model migration - and only by creating/overriding the get_profile() function with something like my_user.userprofile_set.all()[0]) somewhere?
Anyone out there that has gone down this path and can share ideas or experiences?
If I where to do this service again now - would obviously not go this way but with a semi-large live production system I am open for short-cuts :-)
Using a profile model with a relation to the built-in User is still a totally legitimate construct for storing additional user information (and recommended in many cases). The AUTH_PROFILE_MODULE and get_profile() stuff that is now deprecated just ended up being unnecessary, given that built-in Django 1-to-1 syntax works cleanly and elegantly here.
The transition from the old usage is actually easy if you're already using a OneToOneField to User on your profile model, which is how the profile module was recommended to be set up before get_profile was deprecated.
class UserProfile(models.Model):
user = OneToOneField(User, related_name="profile")
# add profile fields here, e.g.,
nickname = CharField(...)
# usage: no get_profile() needed. Just standard 1-to-1 reverse syntax!
nickname = request.user.profile.nickname
See here if you're not familiar with the syntactic magic for OneToOneField's that makes this possible. It ends up being a simple search and replace of get_profile() for profile or whatever your related_name is (auto related name in the above case would be user_profile). Standard django reverse 1-1 syntax is actually nicer than get_profile()!
Change a ForeignKey to a OneToOneField
However, I realize this doesn't answer your question entirely. You indicate that you used a ForeignKey to User in your profile module rather than a OneToOne, which is fine, but the syntax isn't as simple if you leave it as a ForeignKey, as you note in your follow up comment.
Assuming you were using your ForeignKey in practice as an unique foreign key (essentially a 1-to-1), given that in the DB a OneToOneField is just a ForeignKey field with a unique=True constraint, you should be able to change the ForeignKey field to a OneToOneField in your code without actually having to make a significant database migration or incurring any data loss.
Dealing with South migration
If you're using South for migrations, the code change from the previous section may confuse South into deleting the old field and creating a new one if you do a schemamigration --auto, so you may need to manually edit the migration to do things right. One approach would be to create the schemamigration and then blank out the forwards and backwards methods so it doesn't actually try to do anything, but so it still freezes the model properly as a OneToOneField going forward. Then, if you want to do things perfectly, you should add the unique constraint to the corresponding database foreign key column as well. You can either do this manually with SQL, or via South (by either editing the migration methods manually, or by setting unique=True on the ForeignKey and creating a first South migration before you switch it to a OneToOneField and do a second migration and blank out the forwards/backwards methods).
By default, Django's admin renders ForeignKey fields in admin as a select field, listing every record in the foreign table as an option. In one admin-accessible model, I'm referencing the User model as a ForeignKey, and since I have thousands of users Django is populating the select with thousands of options. This is causing the admin page to load incredibly slowly, and the select is not very useful since it can take a while to scroll through thousands of options to find the one you want.
What's the best way to change the rendering of this field in order to improve page load and usability? I'd like the select field to be replaced with some sort of button to launch a search form popup, or a text field that searches keywords via Ajax to find the Id for the specific User they want to associate. Does admin have anything like this builtin, or would I have to write this from scratch?
Add raw_id_fields to your model to only show the ID instead of a dropdown.
You're right, Cerin, the cause of the slowdown is because Django is populating the <select> element with too many options. You might want to use an autocomplete element instead.
Interestingly, Django 2.0 has introduced a new feature on the admin site, called autocomplete_fields, which I think you will find useful in this case. It uses AJAX.
class ExampleAdmin(models.ModelAdmin):
autocomplete_fields = ['example_field_user']
You can use one of the few autocomplete apps for Django. Check them at Django Packages.
There's also django-extensions that have ForeignKeyAutocompleteAdmin that fit your needs pretty well.
Another option is to add readonly_fields instead of raw_id_fields