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
Related
Is there a possibility to check that an instance detail is being opened in django admin?
An example with orders illustrates it well - imagine that there is a new order instance in a list display of order instances in django admin. (data come from outside django)
The order model has the following field which is blank when the instance appears in the list;
ProcessingPersonAdminId = models.IntegerField(verbose_name=_('Processing Person Admin Id'), db_column='ProcessingPersonAdminId', blank=True, null=True)
What I need to do - first person (imagine from sales dep.) that clicks on this particular order instance to view its detail is assigned to it. Before it even displays so the field is already filled in with corresponding user data.
I was thinking about signals but nothing is being saved or updated neither in admin nor in models or anywhere else.
I would appreciate any hint on how to approach this task. Thank you in advance.
Solution 1
Do you mean when the changeview page has been opened in the admin app? If so you can do this:
class OrderModelAdmin(admin.ModelAdmin)
def changeform_view(self, request, *args, **kwargs):
user = request.user()
# do something with user
return super().changeform_view(request, *args, **kwargs)
However, is this really what you want to do? Imagine someone accidentally clicks a wrong page. They are automatically assigned. Or maybe someone wants to look at an order without being assigned to it. Quite apart from anything else, this would also go against the principle that a GET request shouldn't change data.
Solution 2
One alternative would be to override the save_model method in your ModelAdmin:
class OrderModelAdmin(admin.ModelAdmin)
def save_model(self, request, obj, form, change):
user = request.user()
obj.id_of_person = user.id
return super().changeform_view(self, request, obj, form, change)
This way, whenever someone uses the admin to make a change to an order, that person is then assigned that order.
Other things to consider
The admin app is not designed to be a production ready, customer facing app. The only people using it should really be devs, who understand the data they are changing. The sales department definitely shouldn't be using the admin app. For this you should write your own views.
I'm relatively new to Django, and I've been trying to find a way to implement a ManyToMany field whose visible 'choices' within the UI change based upon a BooleanField found within the same model.
For instance, suppose I have a model that represents different jobs, and a worker model that has a manytomany relationship to this jobs model. Suppose also that there are two types of workers: a manager and non-manager which is represented as a BooleanField. If you are a manager, you have certain jobs that a worker does not have and vice versa.
I'm trying to find a way, without creating a new table, to have it such that the jobs listed within the manytomany relationship are dependent on the boolean value of 'is_manager'. That is, if you were to click 'is_manager', this should list manager-specific jobs, yet these manager specific jobs live within the same table as non-manager jobs -- those would just be blank.
I've been looking into the through field, etc, but all solutions that I come up with seem to be dependent on making another table. I'm sure there is a way to do this better though.
Thank you.
I suggest this approach:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['jobs', 'username']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get('instance', None)
if instance is not None:
if instance.is_manager:
self.fields['jobs'].queryset = Jobs.objects.filter(manager=True)
else:
self.fields['jobs'].queryset = Jobs.objects.filter(manager=False)
(Django 1.8) I have a table which has 4 many-to-many relationship to other tables.
Two of these tables have so many entries and that is causing the admin page to load very slowly because it is trying to load all the entries in the lists.
Is there a way to avoid the internal admin page query for loading all the entries of the big tables to speed up the admin page load?
I think the best way is to only list the selected values but I'm not sure how.
I'm not sure how to use limit_choices_to in here:
class Data(models.Model):
pass # stuff here
class Report(models.Model):
data= models.ManyToManyField(Data)
I also tried adding this to my admin.py but it did not help at all. It's not limiting for some reason:
def queryset(self, request):
qs = super(MyModelAdmin, self).queryset(request)
if len(qs) > 10:
qs = qs[:10]
return qs
If you still want to use limit_choices_to, then please refer to the docs. You basically just supply the filters in a dictionary object.
To speed up the admin, my suggestions include:
1. Using raw_id_fields in your ModelAdmin. This gives you a little searchbox instead of a selectbox and avoids the overhead of listing all related objects.
2. If you are handling forward ForeignKey relationships, you can use list_select_related in your ModelAdmin as well. In your case, you are handling many-to-many relationships, so you can try overriding the get_queryset method of the ModelAdmin, and use prefetch_related like in the code below.
from django.contrib import admin
class TestModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
test_model_qs = super(TestModelAdmin, self).get_queryset(request)
test_model_qs = test_model_qs.prefetch_related('many-to-many-field')
return test_model_qs
If you really like to get your hands dirty, I highly recommend you use django-debug-toolbar. It really gives you visibility on how many and what SQL statements are being run. If you can read SQL, you can deduce what you need to input to select_related and prefetch_related.
Given a model with ForeignKeyField (FKF) or ManyToManyField (MTMF) fields with a foreignkey to 'self' how can I prevent self (recursive) selection within the Django Admin (admin).
In short, it should be possible to prevent self (recursive) selection of a model instance in the admin. This applies when editing existing instances of a model, not creating new instances.
For example, take the following model for an article in a news app;
class Article(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField()
related_articles = models.ManyToManyField('self')
If there are 3 Article instances (title: a1-3), when editing an existing Article instance via the admin the related_articles field is represented by default by a html (multiple)select box which provides a list of ALL articles (Article.objects.all()). The user should only see and be able to select Article instances other than itself, e.g. When editing Article a1, related_articles available to select = a2, a3.
I can currently see 3 potential to ways to do this, in order of decreasing preference;
Provide a way to set the queryset providing available choices in the admin form field for the related_articles (via an exclude query filter, e.g. Article.objects.filter(~Q(id__iexact=self.id)) to exclude the current instance being edited from the list of related_articles a user can see and select from. Creation/setting of the queryset to use could occur within the constructor (__init__) of a custom Article ModelForm, or, via some kind of dynamic limit_choices_to Model option. This would require a way to grab the instance being edited to use for filtering.
Override the save_model function of the Article Model or ModelAdmin class to check for and remove itself from the related_articles before saving the instance. This still means that admin users can see and select all articles including the instance being edited (for existing articles).
Filter out self references when required for use outside the admin, e.g. templates.
The ideal solution (1) is currently possible to do via custom model forms outside of the admin as it's possible to pass in a filtered queryset variable for the instance being edited to the model form constructor. Question is, can you get at the Article instance, i.e. 'self' being edited the admin before the form is created to do the same thing.
It could be I am going about this the wrong way, but if your allowed to define a FKF / MTMF to the same model then there should be a way to have the admin - do the right thing - and prevent a user from selecting itself by excluding it in the list of available choices.
Note: Solution 2 and 3 are possible to do now and are provided to try and avoid getting these as answers, ideally i'd like to get an answer to solution 1.
Carl is correct, here's a cut and paste code sample that would go in admin.py
I find navigating the Django relationships can be tricky if you don't have a solid grasp, and a living example can be worth 1000 time more than a "go read this" (not that you don't need to understand what is happening).
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['myManyToManyField'].queryset = MyModel.objects.exclude(
id__exact=self.instance.id)
You can use a custom ModelForm in the admin (by setting the "form" attribute of your ModelAdmin subclass). So you do it the same way in the admin as you would anywhere else.
You can also override the get_form method of the ModelAdmin like so:
def get_form(self, request, obj=None, **kwargs):
"""
Modify the fields in the form that are self-referential by
removing self instance from queryset
"""
form = super().get_form(request, obj=None, **kwargs)
# obj won't exist yet for create page
if obj:
# Finds fieldnames of related fields whose model is self
rmself_fields = [f.name for f in self.model._meta.get_fields() if (
f.concrete and f.is_relation and f.related_model is self.model)]
for fieldname in rmself_fields:
form.base_fields[fieldname]._queryset =
form.base_fields[fieldname]._queryset.exclude(id=obj.id)
return form
Note that this is a on-size-fits-all solution that automatically finds self-referencing model fields and removes self from all of them :-)
I like the solution of checking at save() time:
def save(self, *args, **kwargs):
# call full_clean() that in turn will call clean()
self.full_clean()
return super().save(*args, **kwargs)
def clean(self):
obj = self
parents = set()
while obj is not None:
if obj in parents:
raise ValidationError('Loop error', code='infinite_loop')
parents.add(obj)
obj = obj.parent
class dbview(models.Model):
# field definitions omitted for brevity
class Meta:
db_table = 'read_only_view'
def main(request):
result = dbview.objects.all()
Caught an exception while rendering: (1054, "Unknown column 'read_only_view.id' in 'field list'")
There is no primary key I can see in the view. Is there a workaround?
Comment:
I have no control over the view I am accessing with Django. MySQL browser shows columns there but no primary key.
When you say 'I have no control over the view I am accessing with Django. MySQL browser shows columns there but no primary key.'
I assume you mean that this is a legacy table and you are not allowed to add or change columns?
If so and there really isn't a primary key (even a string or non-int column*) then the table hasn't been set up very well and performance might well stink.
It doesn't matter to you though. All you need is a column that is guaranteed to be unique for every row. Set that to be 'primary_key = True in your model and Django will be happy.
There is one other possibility that would be problemmatic. If there is no column that is guaranteed to be unique then the table might be using composite primary keys. That is - it is specifying that two columns taken together will provide a unique primary key. This is perfectly valid relational modelling but unfortunatly unsupported by Django. In that case you can't do much besides raw SQL unless you can get another column added.
I have this issue all the time. I have a view that I can't or don't want to change, but I want to have a page to display composite information (maybe in the admin section). I just override the save and raise a NotImplementedError:
def save(self, **kwargs):
raise NotImplementedError()
(although this is probably not needed in most cases, but it makes me feel a bit better)
I also set managed to False in the Meta class.
class Meta:
managed = False
Then I just pick any field and tag it as the primary key. It doesn't matter if it's really unique with you are just doing filters for displaying information on a page, etc.
Seems to work fine for me. Please commment if there are any problems with this technique that I'm overlooking.
If there really is no primary key in the view, then there is no workaround.
Django requires each model to have exactly one field primary_key=True.
There should have been an auto-generated id field when you ran syncdb (if there is no primary key defined in your model, then Django will insert an AutoField for you).
This error means that Django is asking your database for the id field, but none exists. Can you run django manage.py dbshell and then DESCRIBE read_only_view; and post the result? This will show all of the columns that are in the database.
Alternatively, can you include the model definition you excluded? (and confirm that you haven't altered the model definition since you ran syncdb?)
I know this post is over a decade old, but I ran into this recently and came to SO looking for a good answer. I had to come up with a solution that addresses the OP's original question, and, additionally, allows for us to add new objects to the model for unit testing purposes, which is a problem I still had with all of the provided solutions.
main.py
from django.db import models
def in_unit_test_mode():
"""some code to detect if you're running unit tests with a temp SQLite DB, like..."""
import sys
return "test" in sys.argv
"""You wouldn't want to actually implement it with the import inside here. We have a setting in our django.conf.settings that tests to see if we're running unit tests when the project starts."""
class AbstractReadOnlyModel(models.Model):
class Meta(object):
abstract = True
managed = in_unit_test_mode()
"""This is just to help you fail fast in case a new developer, or future you, doesn't realize this is a database view and not an actual table and tries to update it."""
def save(self, *args, **kwargs):
if not in_unit_test_mode():
raise NotImplementedError(
"This is a read only model. We shouldn't be writing "
"to the {0} table.".format(self.__class__.__name__)
)
else:
super(AbstractReadOnlyModel, self).save(*args, **kwargs)
class DbViewBaseModel(AbstractReadOnlyModel):
not_actually_unique_field = IntegerField(primary_key=True)
# the rest of your field definitions
class Meta:
db_table = 'read_only_view'
if in_unit_test_mode():
class DbView(DbViewBaseModel):
not_actually_unique_field = IntegerField()
"""This line removes the primary key property from the 'not_actually_unique_field' when running unit tests, so Django will create an AutoField named 'id' on the table it creates in the temp DB that it creates for running unit tests."""
else:
class DbView(DbViewBaseModel):
pass
class MainClass(object):
#staticmethod
def main_method(request):
return DbView.objects.all()
test.py
from django.test import TestCase
from main import DbView
from main import MainClass
class TestMain(TestCase):
#classmethod
def setUpTestData(cls):
cls.object_in_view = DbView.objects.create(
"""Enter fields here to create test data you expect to be returned from your method."""
)
def testMain(self):
objects_from_view = MainClass.main_method()
returned_ids = [object.id for object in objects_from_view]
self.assertIn(self.object_in_view.id, returned_ids)