Django admin: Prefetch choices in list_editable - python

I have a model Domain and I set it's ForeignKey Language model in list_editable in ModelAdmin.
The problem is that it causes lot of SQL queries according to django_debug_toolbar. I thought that I could solve it using select_related but it did not help because it selects only actual values, not all choices.
#register(Domain)
class DomainAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'main_url', 'language', 'max_depth', 'number_of_urls']
list_editable = ['name', 'main_url', 'language', 'max_depth']
list_select_related = ['language']
#def get_queryset(self, request):
# return super(DomainAdmin, self).get_queryset(request).prefetch_related('language')
It still perform SQL query for every Domain to fetch all Language objects.
How to make it fetch once for all Domains?

Django-cachalot is especially efficient in the Django administration website since it’s unfortunately badly optimised (use foreign keys in list_editable if you need to be convinced).
https://django-cachalot.readthedocs.io/en/latest/introduction.html

Related

Django-Tteebeard using treadbeard field forms in django forms

I am trying to use TreeBeard's built in Form's with django forms (not admin). I specifically wanted to replace the rendering of a Select ForeignKey field with TreeBeard forms format. I thought I could do this by declaring the field in my ModelForm, but I've had no success. I'm new to django so my understanding is limited.
These are my initial classes in my forms.py
MyCategories = movenodeform_factory(Category)
class CreatePost(ModelForm):
class Meta:
model = Post
fields = ['title', 'category', 'region', 'content', ]
I tried implementing it by declaring the category field in the beginning but this clearly isn't the way to do it. The declaration does return an html formatted category list, but I can't replace the Post category (which is a ForeignKey)with it.
class CreatePost(ModelForm):
category = movenodeform_factory(Category)
class Meta:
model = Post
fields = ['title', 'category', 'region', 'content', ]
The reason I want to use TreeBeard forms is because of the way it nests the fields according to the category hierarchy.
SOLVED:
This ended up being much simpler than I realized.
class CreatePost(ModelForm):
CHOICES = MoveNodeForm.mk_dropdown_tree(Category)
category = ChoiceField(choices=CHOICES)
class Meta:
model = Post
fields = ['title', 'category', 'region', 'content', ]
The solution was right in front of me. I just needed to create a list using mk_dropdown_tree and use it in a ChoiceField. I hope this might help someone someday.
class CreatePost(ModelForm):
CHOICES = MoveNodeForm.mk_dropdown_tree(Category)
category = ChoiceField(choices=CHOICES)
class Meta:
model = Post
fields = ['title', 'category', 'region', 'content', ]

Limiting choices in foreign key dropdown in Django using Generic Views ( CreateView )

I've two models:
First one:
class A(models.Model):
a_user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
a_title = models.CharField("A title", max_length=500)
Second one:
class B(models.Model):
b_a = models.ForeignKey(A, verbose_name=('A'), unique=False, on_delete=models.CASCADE)
b_details = models.TextField()
Now, I'm using CreateView to create form for Value filling :
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
Then using this to render these field in templates.
Now, my problem is, while giving the field b_a ( which is the dropdown ), it list downs all the values of model A, but the need is to list only the values of model A which belongs to the particular logged in user, in the dropdown.
I've seen all the answers, but still not able to solve the problem.
The things I've tried:
limit_choices_to in models : Not able to pass the value of A in the limit_choices
form_valid : Don't have the model A in the CreateView, as only B is reffered model in B_Create
passing primary key of A in templates via url : Then there is no instance of A in the template so can't access. Also, don't want to handle it in templates.
I'm new to Django and still learning, so don't know to override admin form.
Please suggest the implemented way, if possible to the problem. I've researched and tried most of the similar questions with no result for my particular problem. I feel like, this is a dumb question to ask, but I'm stuck here, so need help.
Thanks..
(Please feel free to suggest corrections.)
You have access to self.request.user in the form_valid of the view. But in order to limit the choices in the form you have to customize the form before it is served initially. You best override the view's get_form and set the form field's queryset:
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
def get_form(self, *args, **kwargs):
form = super(B_Create, self).get_form(*args, **kwargs)
form.fields['b_a'].queryset = self.request.user.a_set.all()
# form.fields['b_a'].queryset = A.objects.filter(a_user=self.request.user)
return form
Generally, there are three places where you can influence the choices of a ModelChoiceField:
If the choices need no runtime knowledge of your data, user, or form instance, and are the same in every context where a modelform might be used, you can set limit_choices_to on the ForeignKey field itself; as module level code, this is evaluated once at module import time. The according query will be built and executed every time a form is rendered.
If the choices need no runtime knowledge, but might be different in different forms, you can use custom ModelForms and set the queryset in the field definition of the respective form field.
If the queryset needs any runtime information, you can either override the __init__ of a custom form and pass it any information it needs to set the field's queryset or you just modify the queryset on the form after it is created which often is a quicker fix and django's default views provide nice hooks to do that (see the code above).
The #schwobaseggl answer is excellent.
Here is a Python 3 version. I needed to limit the projects dropdown input based on the logged-in user.
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
template_name = 'brand/product-create.html'
fields = '__all__'
def get_form(self, form_class=None):
form = super().get_form(form_class=None)
form.fields['project'].queryset = form.fields['project'].queryset.filter(owner_id=self.request.user.id)
return form

Update Model Instance via Django REST POST

I've got a model "Assignment" that I want to be able to update through the api.
Updated per comments
urls.py
router = routers.DefaultRouter()
router.register(r'assignments', views.AssignmentList, base_name='Assignments')
urlpatterns = [
url(r'^api/v1/', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
serializers.py
class AssignmentSerializer(serializers.ModelSerializer):
class Meta:
model = Assignment
fields = (
'id', 'company', 'farm', 'sensor', 'name', 'location', 'notes', 'units', 'max_height', 'frequency',
'interval', 'max_threshold', 'min_threshold', 'notify', 'last_alert_time', 'last_alert_water_level')
views.py
class AssignmentList(viewsets.ModelViewSet):
serializer_class = AssignmentSerializer
pagination_class = None
def get_queryset(self):
queryset = Assignment.objects.all()
company_id = self.request.query_params.get('company_id', None)
sensor_id = self.request.query_params.get('sensor_id', None)
if company_id is not None:
queryset = Assignment.objects.filter(company_id=company_id)
if sensor_id is not None:
queryset = Assignment.objects.filter(sensor_id=sensor_id)
return queryset
Currently my view allows for easy filtering based on two of the fields, 'company_id' and 'sensor_id'. This allows for easy access of the data in json. Unfortunately I can't figure out how to POST back even with the built in API form. I would like to be able to filter down to a single instance and edit a single field, let's say "assignment.name" for now.
It's my understanding that...
The actions provided by the ModelViewSet class are .list(), .retrieve(), .create(), .update(), .partial_update(), and .destroy(). (DRF Docs)
So what do I need to do to leverage them to edit a model instance via url? Or honestly just edit one period. I've been running in circles trying different Mixins, views (UpdateAPIView, RetrieveUpdateAPIView etc.) and this question in particular Stack Overflow: Django Rest Framework update field.
Why you're lacking the required foreign key fields is entirely the question.
The reason is that presumably your fields are called company, farm and sensor, not company_id etc. Because the underlying database fields are called company_id and so on, DRF detects them as read-only properties on the model and so allows you to specify those names in the fields tuple without showing an error, but doesn't display the field in the browsable API. Change the fields tuple to include the actual names of the Django fields.
Note also that since you're using HyperlinkedModelSerializer, DRF it expects a linked ViewSet for those related models. If you don't have those defined, chnage it to a basic ModelSerializer.

How to use custom field for search in django admin

i have a model and is registered to the admin and i have used custom field to display in the list
class ReportsAdmin(admin.ModelAdmin):
def investment(self, inst):
return models.OrderDetail.objects.filter(user=inst.user).distinct().count()
list_display = ['investment']
search_fields = ['investment']
i want to search using the investment field in django admin but always getting Cannot resolve keyword 'investment' into field. choices are the Model fields.
is there any way by which i can search using the investment field?
In software, anything is possible... SMH at the accepted answer. You have to override get_search_results.
from django.db.models import Count
class ReportsAdmin(admin.ModelAdmin):
def investment(self, inst):
return models.OrderDetail.objects.filter(user=inst.user).distinct().count()
list_display = ['investment']
search_fields = ['investment']
def get_search_results(self, request, queryset, search_term):
# search_term is what you input in admin site
search_term_list = search_term.split(' ') #['apple','bar']
if not any(search_term_list):
return queryset, False
if 'investment' in search_term_list:
queryset = OrderDetail.objects.annotate(
user_count=Count('user')
).filter(user_count__gte=search_term_list['investment'])
return queryset, False
Well, this is not allowed:
ModelAdmin.search_fields
Set search_fields to enable a search box on the admin change list
page. This should be set to a list of field names that will be
searched whenever somebody submits a search query in that text box.
These fields should be some kind of text field, such as CharField or
TextField. You can also perform a related lookup on a ForeignKey or
ManyToManyField with the lookup API “follow” notation:
You don't have such a field at all (never mind that the field has to be a TextField or CharField). What you actually have is a method in your admin class, which cannot be searched at the database level. Ie what's in the search_fields translates to like '%search_term%' type queries executed at the db.

Django adminsite customize search_fields query

In the django admin you can set the search_fields for the ModelAdmin to be able to search over the properties given there. My model class has a property that is not a real model property, means it is not within the database table. The property relates to another database table that is not tied to the current model through relations.
But I want to be able to search over it, so I have to somehow customize the query the admin site creates to do the filtering when the search field was filled - is this possible and if, how?
I can query the database table of my custom property and it then returns the ids of the model classes fitting the search. This then, as I said, has to flow into the admin site search query.
Thanks!
Since django 1.6, you can customize the search by defining a get_search_results method in your ModelAdmin subclass.
It is well explained in django documentation. The following example is copied from this doc.
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'age')
search_fields = ('name',)
def get_search_results(self, request, queryset, search_term):
queryset, use_distinct = super(PersonAdmin, self).get_search_results(request, queryset, search_term)
try:
search_term_as_int = int(search_term)
queryset |= self.model.objects.filter(age=search_term_as_int)
except:
pass
return queryset, use_distinct
You should use search_fields = ['foreign_key__related_fieldname']
like search_fields = ['user__email'] in the admin class that extends admin.ModelAdmin
read more here
this might can help
search_fields = ['foreign_key__related_fieldname']
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields

Categories