Django admin inline with custom queryset - django

I've got two models:
class Parent:
...
class Child:
parent = models.ForeignKey(Parent)
In the model admin of the Parent I want to show an inline of the Child with a custom queryset, not only the ones related to the parent through the fk field.
I've tried:
class ChildInline(admin.TabularInline):
model = Child
def get_queryset(self, request):
return Child.objects.filter(<my custom filter>)
class ParentAdmin(admin.ModelAdmin):
inlines = [ChildInline]
But still the only children shown in the inline are the ones that fullfill both filters: related to the parent by the FK + my custom filter.
Is it possible to do this?
EDIT:
I've seen now is the BaseInlineFormSet who is filtering the queryset I compose to keep only childs related to the parent, any idea how to avoid this?
django/forms/models.py
class BaseInlineFormSet(BaseModelFormSet):
...
if self.instance.pk is not None:
qs = queryset.filter(**{self.fk.name: self.instance})
...

You have to override __init__() method of BaseInlineFormSet and update queryset there.
from django.forms.models import BaseInlineFormSet
class ChildInlineFormSet(BaseInlineFormSet):
def __init__(self, *args, **kwargs):
super(ChildInlineFormSet, self).__init__(*args, **kwargs)
# Now we need to make a queryset to each field of each form inline
self.queryset = Child.objects.filter(<my custom filter>)
Then initialise formset attribute with ChildInlineFormSet
class ChildInline(admin.TabularInline):
model = Child
formset = ChildInlineFormSet
extra = 0

The old answer doesn't work anymore for current Django 2.2 or 3 because self.queryset get ignored
Current solution is to override the get_queryset:
from django.forms.models import BaseInlineFormSet
class ChildInlineFormSet(BaseInlineFormSet):
def get_queryset(self):
qs = super(ChildInlineFormSet, self).get_queryset()
return qs.filter(<custom query filters>)
class ChildInline(admin.TabularInline):
model = Child
formset = ChildInlineFormSet
extra = 0

Related

for loop of ListView not iterating through data in Django

code which not working
this code is not working, i don't know what is wrong in my code.
Views.py
You didn't define the model name on the view.Add the model name and try to add custom context_object_name. For reference check this
class BookListView(generic.ListView):
model = Book
context_object_name = 'my_book_list' # your own name for the list as a template variable
queryset = Book.objects.filter(title__icontains='war')[:5] # Get 5 books containing the title war
template_name = 'books/my_arbitrary_template_name_list.html' # Specify your own template name/location
You have to specify which model to create ListView. Here in your case define model=Post as
from django.views.generic.list import ListView
class PostList(ListView):
model = Post
template_name = 'blog/index.html'
queryset = Post.objects.filter(status=1).order_by("-created_on")
Or you can also use get_queryset() as
from django.views.generic.list import ListView
class PostList(ListView):
# specify the model
model = Post
template_name = 'blog/index.html'
def get_queryset(self, *args, **kwargs):
qs = super(PostList, self).get_queryset(*args, **kwargs)
qs = qs.filter(status=1).order_by("-created_on")
return qs

Give a ForeignKey Form Field a value of a Queryset

So I have two Models that I want to relate with a ForeignKey. One of the ModelForms I want to have it's Foreign Key field pre populated before the model gets created. The info from the ForeignKey comes from a ListView (List of Cars that belong to clients) template.
MODELS.PY
class ClientCar(models.Model):
license_plate = models.CharField(max_length=20, unique=True, name='license_plate')
def__str__:
pk = self.pk
license_plate = self.license_plate
return f"pk:{pk} license_plate {license_plate}"
class CarDetail(model.Model):
car = models.ForeignKey(ClientCar, on_delete=models.CASCADE, null=False)
detail = models.CharField(max_length=40, null=False)
So the ListView template will have the basic crud of the Car model but I also want to add a "Wash button", the wash button will pass the selected Car's pk to the CarDetail Form template. It is here where I am having issues. I can Query the PK of the car from Kwargs but I can't seem to populate the Form's field with that query or have it render on the template.
VIEWS.PY
class WashService(LoginRequiredMixin, CreateView):
model = CarDetail
form_class = WashServiceForm
template_name = 'service_app/standard_wash_form.html'
def get_form_kwargs(self, *args, **kwargs):
kwargs = super(WashService, self).get_form_kwargs(*args, **kwargs)
ctd = ClientCar.objects.filter(pk=self.kwargs.get('pk')).values('license_plate')
kwargs['initial']['car'] = ctd
return kwargs
I have researched this and came to the understanding that in the Form for creating this model I have to overwrite the _ _ init _ _ function, I'm not really sure how to solve this since I don't know how to call the kwargs passed from the Listview template from the forms.py
If you can guide me with some snippets or anything I'm greatful.
Thanks in advance.
I think it makes more sense to simply change what function the ModelChoiceField uses for the choices. We can first make a subclass of ModelChoiceField for the car, to select this by license plate:
from django import forms
class CarByLicensePlateChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.license_plate
Then in your WashServiceForm we can use this field:
class WashServiceForm(forms.ModelForm):
car = CarByLicensePlateChoiceField(queryset=Car.objects.all())
class Meta:
model = Car
fields = ['car', 'detail']
In your CreateView, you can then populate the car with the Car that belongs to the given primary key:
from django.shortcuts import get_object_or_404
class WashService(LoginRequiredMixin, CreateView):
model = CarDetail
form_class = WashServiceForm
template_name = 'service_app/standard_wash_form.html'
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs(*args, **kwargs)
initials = kwargs.setdefault('initial', {})
intial['car'] = get_object_or_404(Car, pk=self.kwargs['pk'])
return kwargs

Creating a generic search view in Django

I am struggling to create my custom generic view in django to easily create search pages for certain models. I'd like to use it like this:
class MyModelSearchView(SearchView):
template_name = 'path/to/template.html'
model = MyModel
fields = ['name', 'email', 'whatever']
which will result in a view that returns a search form on GET and both form and results on POST.
The fields specifies which fields of MyModel will be available for a user to search.
class SearchView(FormView):
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
return SearchForm()
def post(self, request, *args, **kwargs):
# perform searching and return results
The problem with the code above is that form will not be submitted if certain fields are not be properly filled. User should be allowed to provide only part of fields to search but with the code I provided the form generated with ModelForm prevents that (for example because a field in a model cannot be blank).
My questions are:
Is it possible to generate a form based on a model to omit this behaviour?
Or is there any simpler way to create SearchView class?
I don't want to manually write forms if it's possible.
One way to accomplish this is to set blank=True on the field in MyModel, as indicated in the docs:
If the model field has blank=True, then required is set to False on the form field. Otherwise, required=True.
But for this to be a generic solution, you can't count on being able to modify the model fields. You can instead set the fields' required attribute to False immediately after the instance is created:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for (field_name, field) in self.fields.items():
field.required = False
Since you're using the ModelForm for searching, you should set all the fields as required=False, by overriding the __init__ method:
def get_form(self, form_class=None):
# what I'v already tried:
class SearchForm(ModelForm):
class Meta:
model = self.model
fields = self.fields
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for field in self.fields:
self.fields[field].required = False
return SearchForm()
Though I suggest you should user django-filter, which makes it easier and cleaner to filter your searches. First you need to install it:
pip install django-filter
Then add it to your INSTALLED_APPS. After that you can create a filters.py file in your app:
# myapp/filters.py
import django_filters as filters
from .models import MyModel
MyModelFilterSet(filters.FilterSet):
class Meta:
model = MyModel
fields = ['name', 'email', 'whatever']
By default it's going to filter with the __exact lookup. You can change this in a couple of ways, just take a look here and here. To know which lookups you can use, take a look here.
After creating your filters.py file you can add it to a View, like a ListView:
# myapp/views.py
from django.views.generic import ListView
from .filters import MyModelFilterSet
from .models import MyModel
class MyModelSearchView(ListView):
template_name = 'path/to/template.html'
model = MyModel
def get_queryset(self):
qs = self.model.objects.all()
filtered_model_list = MyModelFilterSet(self.request.GET, queryset=qs)
return filtered_model_list.qs
There's a lot more you can do with django-filter. Here's the full documentation.

Hidden property in Django 1.7 admin BaseInlineFormSet

I have a model in my project which is rendered as an inline of an other model.
The inline model has a property (not a field) that I need in my template for a particular check, but I don't want it shown as a field. I can't find a way to make such field rendered as hidden, so that the entire <TD> disappears.
I've both tried with Meta class:
class MyFormset(forms.models.BaseInlineFormSet):
class Meta:
widgets = {
'to_be_hidden_property': forms.HiddenField,
}
and with add_fields method:
class MyFormset(forms.models.BaseInlineFormSet):
def add_fields(self, form, index):
super(MyFormset, self).add_fields(form, index)
form.fields["to_be_hidden_property"].widget = forms.HiddenInput()
but both failed. First attempt simply gets ignored, while second one leads to a KeyError exception (field doesn't exist).
I'm open to suggestions, as I'd really hate to have to hide it via JS.
EDIT (adding admin.TabularInline code):
class AssegnamentoCommessaConsulenzaInline(admin.TabularInline):
formset = AssegnamentoCommessaConsulenzaFormset
model = AssegnamentoCommessaConsulenza
template = 'admin/commessa/edit_inline/assegnamentocommessaconsulenza_tabular.html'
extra = 0
readonly_fields = (
'get_fase',
'get_configuration_link_inline',
'numero_erogazioni_effettuate', # Field to hide
)
class Media:
js = ('useless things', )
css = {'all': ('even less useful things', )}
EDIT (to explain what I've tried after #Alasdair's suggestion):
from django.forms.models import inlineformset_factory
class MyFormAbstract(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyFormAbstract, self).__init__(*args, **kwargs)
self.fields['to_be_hidden_property'] = forms.CharField(
widget=forms.HiddenInput(),
initial=self.instance.to_be_hidden_property
)
class Meta:
model = MyModel
fields = '__all__'
MyFormset = inlineformset_factory(
MyMainModel,
MyInlineModel,
form=MyFormAbstract
)
This whole thing seems to get silently ignored. There are no errors, but the property is still there.
Instead of subclassing BaseInlineFormSet, you might find it easier to customize the model form.
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs)
self.fields['property_field'] = forms.CharField(widget=forms.HiddenInput(), initial=self.instance.property_field)
MyInlineFormset = inlineformset_factory(MyMainModel, MyModel, form=MyForm)
Note that all this does is add the hidden field to the form, you will have to add any additional processing you want.

Subclassing Django ListView

I'm using Django v1.4 and I'm trying to subclass the generic ListView view. Here's the code
from django.views.generic import ListView
class SearchListView(ListView):
model = None
fields = None
def get_queryset(self):
#...etc...
return super(SearchListView, self).get_queryset()
Then I'll further customize that view for a particular model:
class PersonSearchListView(SearchListView):
model = Person
fields = ['first_name', 'last_name']
So what happens is, ImproperlyConfigured exceptions are the superclass (ListView) stating that either model or queryset should be defined. I thought I was... (model = Person). Why is this value not making it into the view?
Thanks
When you call super(SearchListView, self).get_queryset()
You will call the get_queryset of the below class, as you can see it will raise an exception if you didn't set the model or queryset.
ListView is a child of MultipleObjectMixin.
But if you instantiate a PersonSearchListView, the model should have been set correctly. Could you include the url config? Will try it out later and update my answer.
class MultipleObjectMixin(ContextMixin):
"""
A mixin for views manipulating multiple objects.
"""
allow_empty = True
queryset = None
model = None
paginate_by = None
context_object_name = None
paginator_class = Paginator
def get_queryset(self):
"""
Get the list of items for this view. This must be an iterable, and may
be a queryset (in which qs-specific behavior will be enabled).
"""
if self.queryset is not None:
queryset = self.queryset
if hasattr(queryset, '_clone'):
queryset = queryset._clone()
elif self.model is not None:
queryset = self.model._default_manager.all()
else:
raise ImproperlyConfigured("'%s' must define 'queryset' or 'model'"
% self.__class__.__name__)
return queryset