Django form override ForeignKey queryset - django

I was making a form for creation unpublished Artist instances and then adding Artwork to the artist before publishing.
I have manager to hide published=False artists and do not know how to bypass this yet in ForeignKey.
models.py
class PublishedManager(models.Manager):
"""Returns only published Artists in queryset"""
def get_query_set(self):
qs = super(VisibleManager, self).get_query_set()
qs = qs.filter(status='published')
return qs
class Artist(models.Model):
objects = PublishedManager() # Used overall on the site to hide published=False objects
admin_objects = models.Manager() # Default manager Will be used in admin to show invisible objects too
class Artwork(models.Model):
artist= models.ForeignKey(Artist)
forms.py
class ArtworkForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ArtworkForm,self).__init(args,kwargs)
from django.db import models
self.fields['artist'].queryset = Artist.admin_objects.all()
shell
>>> form=Artwork_form(artist=180) #unpublished artist pk
>>> form.errors
Model artist with pk 180 does not exist
I need to make the form "see" the unpublished artists in FK, how can i achieve this?
Thank you.

I found the solution!!!
Original info is here http://www.hoboes.com/Mimsy/hacks/custom-managers-django-foreignkeys/
I implemented the CustomManagerForeignKey as the autor of this post had written with the one exception(otherwise in won't work properly):
usage:
class Artwork(models.Model):
artist= CustomManagerForeignKey(Artist, manager=Artist.admin_objects)
and a fix for this in the CustomManagerForeignKey:
class CustomManagerForeignKey(models.ForeignKey):
def __init__(self, *args, **kwargs):
if 'manager' in kwargs:
self.customManager = kwargs['manager'] #Here i removed the () in the end of the line
del kwargs['manager']
else:
self.customManager = None
super(CustomManagerForeignKey, self).__init__(*args, **kwargs)

Related

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.

Limit the Choices shown from ManyToMany ForeignKey

How do I limit the values returned via the ManyToMany relationship and thus displayed in the <SELECT> field on my form to only show the spots which were created by the currently logged in user?
models.py
class Project(models.Model):
owner = models.ForeignKey(User, editable=False)
...
spots = models.ManyToManyField(to='Spot', blank=True, )
class Spot(models.Model):
owner = models.ForeignKey(User, editable=False)
spot_name = models.CharField(max_length=80, blank=False)
forms.py
from django import forms
from .models import Project, Spot
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', )
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
I'm using GenericViews for Update and Create and currently see all of the entries everyone has made into Spots when I'm updating or creating a Project. I want to see only the entries entered by the logged in user. For completeness sake, yes, the project.owner and spot.owner were set to User when they were created.
I've tried def INIT in the forms.py and using limit_choices_to on the manytomany field in the model. Either I did those both wrong or that's not the right way to do it.
thank you!
in your forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', )
def __init__(self, user_id, *args, **kwargs):
self.fields['spots'] = forms.ModelChoiceField(widget=forms.Select, queryset=Project.objects.filter(owner=user_id))
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
def __init__(self, user_id, *args, **kwargs):
self.fields['spot_name'] = forms.ModelChoiceField(widget=forms.Select, queryset=Spot.objects.filter(owner=user_id))
in your views.py
user_id = Project.objects.get(owner=request.user).owner
project_form = ProjectForm(user_id)
spot_form = SpotForm(user_id)
As I mentioned above, Dean's answer was really close, but didn't work for me. Primarily because request is not accessible in the view directly. Maybe it is in older Django versions? I'm on 1.9. Thank you Dean, you got me over the hump!
The gist of what's going on is adding User into the kwargs in the View, passing that to the ModelForm, remove User from the kwargs and use it to filter the Spots before the form is shown.
This is the code that worked for my project:
views.py
class ProjectUpdate(UpdateView):
model = Project
success_url = reverse_lazy('projects-mine')
form_class = ProjectForm
def dispatch(self, *args, **kwargs):
return super(ProjectUpdate, self).dispatch(*args, **kwargs)
def get_form_kwargs(self):
kwargs = super(ProjectUpdate, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
forms.py
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
exclude = ('owner', 'whispir_id')
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user')
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['spots'] = forms.ModelMultipleChoiceField(queryset=Spot.objects.filter(owner=user_id))
class SpotForm(forms.ModelForm):
class Meta:
model = Spot
exclude = ('owner', )
def __init__(self, *args, **kwargs):
user_id = kwargs.pop('user')
super(SpotForm, self).__init__(*args, **kwargs)
self.fields['spot_name'] = forms.ModelMultipleChoiceField(queryset=Spot.objects.filter(owner=user_id))

Using a SELECT field for a OneToMany field

let's say I've the following very simple models:
class Customer(models.Model):
name = models.CharField(max_length=50)
class Probe(models.Model):
OwnerInfo = models.CharField(max_length=50)
comments = models.CharField(max_length=50)
customer = models.ForeignKey(Customer, null=True, blank=True)
I've been able to add an InLine to the Admin gui, but I'd like to use a SELECT component, so I can just select several Probes and assign them to the Customer. From this question:
one-to-many inline select with django admin
I know thanks to Luke's answer (last one) that I should create a custom Form and assign it to my ModelAdmin.form but I can not wonder how to tie it all together to make it work.
May anyone help?
Thanks a lot in advance.
OK, I came a step further, and now I've the field added to the Form, like this:
from django.contrib import admin
from django import forms
from web_gui.models import Probe, Customer, Firmware
class CustomerForm(forms.ModelForm):
probes = forms.ModelMultipleChoiceField(queryset=Probe.objects.all())
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.fields['probes'].initial = [p.pk for p in Probe.objects.filter(customer_id=self.instance.pk)]
class Meta:
model = Customer
class CustomerAdmin(admin.ModelAdmin):
form = CustomerForm
admin.site.register(Probe)
admin.site.register(Customer, CustomerAdmin)
admin.site.register(Firmware)
but the initial values specified through "initial" are not being selected. What's wrong now? I assume that next will be to override the save() method to set the Probes on the Customer, am I right?
This is the best solution I've came up with. Let me know if there is any other better way of achieving this:
from django.contrib import admin
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from web_gui.models import Probe, Customer, Firmware
class CustomerForm(forms.ModelForm):
probes = forms.ModelMultipleChoiceField(queryset = Probe.objects.all(), required=False)
probes.widget = FilteredSelectMultiple("Probes",False,attrs={'rows':'10'})
def __init__(self, *args, **kwargs):
super(CustomerForm, self).__init__(*args, **kwargs)
self.fields['probes'].initial = [p.pk for p in Probe.objects.filter(customer_id=self.instance.pk)]
def save(self, force_insert=False, force_update=False, commit=True):
c = super(CustomerForm, self).save(commit=False)
c.probe_set = self.cleaned_data['probes']
c.save()
return c
class Meta:
model = Customer
class CustomerAdmin(admin.ModelAdmin):
form = CustomerForm
admin.site.register(Probe)
admin.site.register(Customer, CustomerAdmin)
admin.site.register(Firmware)

Django admin - remove field if editing an object

I have a model which is accessible through the Django admin area, something like the following:
# model
class Foo(models.Model):
field_a = models.CharField(max_length=100)
field_b = models.CharField(max_length=100)
# admin.py
class FooAdmin(admin.ModelAdmin):
pass
Let's say that I want to show field_a and field_b if the user is adding an object, but only field_a if the user is editing an object. Is there a simple way to do this, perhaps using the fields attribute?
If if comes to it, I could hack a JavaScript solution, but it doesn't feel right to do that at all!
You can create a custom ModelForm for the admin to drop the field in the __init__
class FooForm(forms.ModelForm):
class Meta(object):
model = Foo
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Since the pk is set this is not a new instance
del self.fields['field_b']
class FooAdmin(admin.ModelAdmin):
form = FooForm
EDIT: Taking a hint from John's comment about making the field read-only, you could make this a hidden field and override the clean to ensure the value doesn't change.
class FooForm(forms.ModelForm):
class Meta(object):
model = Foo
def __init__(self, *args, **kwargs):
super(FooForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
# Since the pk is set this is not a new instance
self.fields['field_b'].widget = forms.HiddenInput()
def clean_field_b(self):
if self.instance and self.instance.pk:
return self.instance.field_b
else:
return self.cleaned_data['field_b']
You can also do the following
class FooAdmin(admin.ModelAdmin)
def change_view(self, request, object_id, extra_context=None):
self.exclude = ('field_b', )
return super(SubSectionAdmin, self).change_view(request, object_id, extra_context)
Taken from here Django admin: exclude field on change form only