Django Admin - Many to Many Field - Display only selected options - django

Basically i have two models: collection and Custom_object. A custom_object has some general fields (id, name, value) and could be anything. A collection is like a group of many custom objects, realized as many to many field in django.
E.g. collection
collection_id
collection_name
...
custom_objects = models.ManyToManyField(to=Custom_object, related_name="related_co")
I want to inspect on the admin site some collections, mainly which custom_objects are selected.
The Problem here is that i have more than 20k different custom_objects, which takes a while to load the page and then the next problem is to scroll all 20k entries down to find some selected custom_objects.
For each collection are only about 10-20 custom_objects selected, not so much.
I want to see only the selected options, its not important for me to see see or select one of the other 19.980 options, because selection and deselection is done by cronjob.
Its just important to see selected custom_objects. Any idea how to do this e.g. in admin.py for the collection model class ?
Thanks a lot for bringing in your expertise and knowledge!
I tried something like this:
class collection(admin.ModelAdmin):
search_fields = ['name', 'id']
def formfield_for_manytomany(self, db_field, request, **kwargs):
kwargs["queryset"] = collection.objects.filter( ??? ) ?
return super(collection, self).formfield_for_manytomany(db_field, request, **kwargs)

Related

How can I limit ImageField to a few choices in Django Admin

I have a model like with a file defined like
models.ImageField(upload_to='folder_icons', null=True)
I want to be able to limit the choice of this icon to a few pre created choices.
I there as way I can show the user (staff member) the choices in the django admin perhaps in a dropdown ?
This is similar to where I want a field where you choose between a few different avatars. Is there a custom field somewhere that can do this ?
Thanks
Just as a starting point, you would need to override the ModelAdmin.get_form() method, which will allow you to change the type of input field that Django uses by default for your image field. Here's what it should look like:
from django.forms import Select
class YourModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
# 1. Get the form from the parent class:
form = super(YourModelAdmin, self).get_form(request, obj, **kwargs)
# 2. Change the widget:
form.base_fields['your_image_field'].widget = Select(choices=(
('', 'No Image'),
('path/to/image1.jpg', 'Image 1'),
('path/to/image2.jpg, 'Image 2'),
))
# 3. Return the form!
return form
You'll still have some other considerations - for instance, the path/location of the images themselves (placing them in the settings.MEDIA_ROOT would probably be easiest, or at least the first step in trying to make this work). I can also imagine that you might want a more sophisticated presentation of this field, so that it shows a thumbnail of the actual images (see #Cheche's answer where he suggests select2 - that gets a bit more complicated though, as you'll need to craft a custom widget).
All of that said, in terms of just altering the form field that the admin uses so that it offers a dropdown/select field, rather than a file upload field, this is how you would achieve that.
What you need is a widget to render your choices. You could try with select2 or any django adapt to this, like django-select2. Check this.

Django - How to call localflavor US_STATES in HTML

How do you make a drop down menu that contains all of the options in localflavor's US_STATES?
I can see how to create a model that contains a field that uses the localflavor option US_STATES.
class State(models.Model):
states = models.CharField(max_length=2, choices=US_STATES , null=True, blank=True)
The field state is then in a manytomany relationship to a model called Person. How do you take this a put it in a html page?
In my view, I can only think of doing this.
def get_context_data(self, *args, **kwargs):
context = super(UserProfileUpdateView, self).get_context_data(*args, **kwargs)
context['states'] = State.objects.all()
But this only pulls the existing state options.
1) How do I pull all states into the view?
2) How can I render an html template to use the output of 1? I imagine it has something to do with the 'choices' option, but I haven't ever done that before.
Thanks
You probably want to take a look at Django's forms system. Since what you're doing here is rendering a form (I'm guessing that by "drop down menu" you mean an HTML <select> element with all the states as options), that would be the preferred way to do it.
The localflavor package includes form field classes for working with its data types, including states.

Filtering inputs in Django admin input form

Have found related answers, but cannot find anything covering my specific need. I have only being using Django for about 2 weeks. Have tried tutorial and the Django documentation.
Background. I have a database with different funds. Each fund can have different performance
periods. In each performance period there can be different series. Each Performance period can
have flows for all the series defined within it.
Have set up relational db so that it filters down like that. Problem is that
series names get re-used across performance periods (there is a seperate unique key).
I have models file that looks something like this:
class SeriesFlow(models.Model):
series= models.ForeignKey(Series)
date = models.DateField('date for flow')
value = models.FloatField('Flow pos inflow, neg outflow')
def __unicode__(self):
return str(self.series)
class Series(models.Model):
perf_period = models.ForeignKey(PerformancePeriod)
series = models.CharField(max_length=100)
series_longname = models.CharField(max_length=200, blank=True)
# more fields
def __unicode__(self):
return self.series
In the admin.py file i do the following relevant things:
class SeriesFlowAdmin(admin.ModelAdmin):
fields = ['series', 'date', 'value']
list_display = ['series', 'date', 'value']
list_filter = ['series__perf_period'] #nice __ syntax to go backwards
# and then registering the admin interfaces
admin.site.register(Fund, FundAdmin)
admin.site.register(PerformancePeriod, PerformancePeriodAdmin)
admin.site.register(Profit, ProfitAdmin)
admin.site.register(Series, SeriesAdmin)
admin.site.register(SeriesFlow, SeriesFlowAdmin)
The admin form allows my to filter series flows by the performance period which
is what I wqant to do. when i try to add a series flow I get the three fields
that i want to enter series, date, value. problem is, is that the dropdown
gives the options for all the series in the database. I want to filter the
dropdown for series flow entering page so that it only gives the relevant series.
the series names displayed get reused between different funds and performance periods
so the dropdown is a mess! The filtered performance period is in the url of
the form so it is defiantely available. just cant figure out how to filter for it.
URL for the series flow filtered and flow entry forms are:
admin/fee/seriesflow/?series__perf_period__id__exact=3
admin/fee/seriesflow/add/?_changelist_filters=series__perf_period__id__exact%3D3
I the filtering is definitely still available. now want to make sure that just the relevant series are displayed. I add screen cap showing that series from other performance periods are also displayed in drop-down.
Don't really understand your model relationship but formfield_for_foreignkey is what you need i think
class SeriesFlowAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'series': kwargs['queryset'] = Series.objects.filter(series='xxx')

How to force Django Admin to use select_related?

One of my models is particularily complex. When I try to edit it in Django Admin it performs 1042 queries and takes over 9 seconds to process.
I know I can replace a few of the drop-downs with raw_id_fields, but I think the bigger bottleneck is that it's not performing a select_related() as it should.
Can I get the admin site to do this?
you can try this
class Foo(admin.ModelAdmin):
list_select_related = (
'foreign_key1',
'foreign_key2',
)
https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_select_related
Although dr jimbob's answer makes sense, for my needs, I was able to simply override the get_queryset() method with a one-liner, even selecting a foreign key's foreign key. Maybe this could be helpful to someone.
class MyModelAdmin(admin.ModelAdmin):
model = MyModel
...
def get_queryset(self, request):
return super(MyModelAdmin, self).get_queryset(request).select_related(
'foreign_key1', 'foreign_key2__fk2_foreign_key')
For my particular model, the particularly slow aspect is going through ForeignKeys when they were being displayed in forms, which aren't called using select_related, so that's the part I'm going to speed up.
Looking through the relevant django source, you see in django/contrib/admin/options.py that the method formfield_for_foreignkeys takes each FK db_field and calls the ForeignKey class's formfield method, which is defined in django/db/models/fields/related/ like:
def formfield(self, **kwargs):
db = kwargs.pop('using', None)
defaults = {
'form_class': forms.ModelChoiceField,
'queryset': self.rel.to._default_manager.using(db).complex_filter(self.rel.limit_choices_to),
'to_field_name': self.rel.field_name,
}
defaults.update(kwargs)
return super(ForeignKey, self).formfield(**defaults)
From this, we see if we provide the db_field with a kwargs['queryset'] we can define a custom queryset that will be use select_related (this can be provided by formfield_for_foreignkey).
So basically what we want to do is override admin.ModelAdmin with SelectRelatedModelAdmin and then make our ModelAdmin subclasses of SelectRelatedModelAdmin instead of admin.ModelAdmin
class SelectRelatedModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if 'queryset' in kwargs:
kwargs['queryset'] = kwargs['queryset'].select_related()
else:
db = kwargs.pop('using', None)
kwargs['queryset'] = db_field.rel.to._default_manager.using(db).complex_filter(db_field.rel.limit_choices_to).select_related()
return super(SelectRelatedModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This code sample doesn't cover admin Inlines or ManyToManyFields, or foreign_key traversal in functions called by readonly_fields or custom select_related queries, but a similar approach should work for those cases.
In Django 2.0+, a good way to improve performance of ForeignKey and ManyToMany relationships is to use autocomplete fields.
These fields don't show all related objects and therefore load with many fewer queries.
For the admin edit/change a specific item page, foreign key select boxes may take a long time to load, to alter the way django queries the data for the foreign key:
Django docs on Using formfield_for_foreignkey
Say I have a field called foo on my Example model, and I wish to select ralated bar objects:
class ExampleAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "foo":
kwargs["queryset"] = Example.objects.select_related('bar')
return super().formfield_for_foreignkey(db_field, request, **kwargs)
For the sake of completeness, I would like to add another option that was the most suitable for my use case.
As others have pointed out, the problem is often loading the data for select boxes. list_select_related does not help in this case.
In case you don't actually want to edit the foreign key field via admin, the easiest fix is making the respective field readonly:
class Foo(admin.ModelAdmin):
readonly_fields = ('foreign_key_field1','foreign_key_field2',)
You can still display these fields, there will simply not be a select box, hence Django does not need to retrieve all the select box options from the database.

Optimizing ModelChoiceField query in django Admin (AppEngine)

I have two models: Activity and Place.
The Activity model has a ReferenceProperty to the Place model.
This was working fine until the Place table started growing and now
when trying to edit an Activity via django admin I get a memory error
from Google (it doesn't happen if I remove that field from the Activity
admin's fieldsets)
The widget used to edit the RefrenceProperty uses Place.all() to get
the possible values.
As both Activity and Place are sharded by a city property I would like
to optimize the widget's choice query from Place.all() to just the
relevant places, for example Place.all().filter("city =", )
I couldn't find a way to override the query in the docs and I was
wondering if the above is even possible? and if so, how?
Managed to optimize the query by overriding the admin form:
class ActivityAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ActivityAdminForm, self).__init__(*args, **kwargs)
self.fields['place'].queryset = <... my query ...>
class ActivityAdmin(admin.ModelAdmin):
form = ActivityAdminForm