Django ModelChoice Field Conditional ForeignKey - django

I've surfed most of the afternoon and have been at this particular quandry for a while.
I am trying to figure out how to essentially present a foreign key as a dropdown choice if the user has driven that type of car. For example purposes and to keep this as easy as possible...
Let's say I have aCars, Manufacturers and a UserProfile model.
I have a model for Cars as so...
class Cars(models.Model):
car_name = models.CharField(max_length=80)
class = models.ForeignKey(Manufacturer,null=True,on_delete=models.DO_NOTHING,related_name='car_manufacturer')
I have a model for Manufacturers as so...
class Manufacturers(models.Model):
manu_name = models.CharField(max_length=80)
Then I have a UserProfile model....
class Userprofile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
user_name = models.CharField(max_length=80)
car_owned = models.ForeignKey(Car,null=True,on_delete=models.DO_NOTHING,related_name='car_owned')
All good so far...
I have a view where I am listing all of the Manufacturers and this works fine as well. It shows all of the manufacturers that I would expect in the form view below.
class ManufacturerForm(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Manufacturer.objects.all())
def __init__(self, *args, **kwargs):
super(ManufacturerForm, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
I'm using the FORMVIEW below to display the form...
class ManufacturerView(LoginRequiredMixin,FormView):
form_class = ManufacturerForm
template_name = 'Directory/HTMLNAME.html'
def get_form_kwargs(self):
kwargs = super(ManufacturerView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
manufacturer = form.cleaned_data['dropdown']
return HttpResponseRedirect(reverse("NAME:manufacturer",kwargs={'pk':manufacturer.pk}))
This all works fine. However, I can't figure out how to limit the manufacturer dropdown to only the cars the user has driven. I'm trying to essentially limit the dropdown display to only the manufacturers that are pertinent to the cars the user has owned based on their profile. I've researched reverse look ups and have also tried something similar to what is outlined below to solve my problem...
class ManufacturerForm(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Manufacturer.objects.filter(car_manufacturer=1)
def __init__(self, *args, **kwargs):
super(ManufacturerForm, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
But this obviously only gives me record 1 for the Manufacturer model. I am trying to figure out how to display only the records that are relevant to an individual user based on their car_owned data. I can list all of the manufacturers and then just display the records that are relevant in a ListView, but I am trying to limit the dropdown to only where there are relevant records in a ListView. Thanks in advance for any thoughts.

You missed just couple of points:
1) Pass UserProfile as kwargs['user']:
kwargs['user'] = UserProfile.objects.get(user=self.request.user)
2) Add user parameter in form's __init__ signature and override dropdown.queryset there:
class ManufacturerForm(forms.Form):
dropdown = forms.ModelChoiceField(queryset=Manufacturer.objects.all())
def __init__(self, user, *args, **kwargs):
super(ManufacturerForm, self).__init__(*args, **kwargs)
self.fields['dropdown'].widget.attrs['class'] = 'choices1'
self.fields['dropdown'].empty_label = ''
self.fields['dropdown'].queryset = Manufacturer.objects.filter(car_manufacturer__car_owned=user)
Also I would like to recommend to rewrite your Car - User relationship to ManyToMany. If I understand correctly your message, User can have multiple cars:
limit the dropdown display to only the manufacturers that are
pertinent to the cars the user has owned
Also if I understand correctly, you want to track cars that user used to have (but doesn't have anymore).
If you rewrite Car - User relationship, then you won't probably have any reason to keep UserProfile model only to hold additional username. If so, your models.py should look like this:
class Manufacturer(models.Model):
name = models.CharField(max_length=80)
class Car(models.Model):
name = models.CharField(max_length=80)
klass = models.ForeignKey(Manufacturer, null=True, on_delete=models.DO_NOTHING, related_name='car_manufacturer')
owners = models.ManyToManyField(User, through='Ownership')
class Ownership(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
car = models.ForeignKey(Car, on_delete=models.CASCADE)
active = models.BooleanField(default=True) # True if user owns the car at the moment

I think it all hinges on your order and relationships between models, perhaps try adding in a ManyToMany relation between Manufacturer and Car, so one manufacturer can make many cars:
class Manufacturer(models.Model):
name = models.CharField(max_length=80)
car = models.ManyToManyField(Car)
Then it may be a case of doing something such as:
qs = Manufacturer.objects.all(car=user.car_owned)
dropdown = forms.ModelChoiceField(qs)
And in your views.py file:
form = ManufacturerForm(request.POST, user=request.user)
(You may need to look up if the above is valid, as I'm not sure if Forms can have the request object passed in).
Disclaimer: I see you're using a class based view...so I may need to tweak the above.

Related

Django (DRF) select related on is_valid()?

My problem is that when trying to run is_valid() on a big chunk of data in a POST-request, where the model has a foreign key, it will fetch the foreign key table for each incoming item it needs to validate.
This thread describes this as well but ended up finding no answer:
Django REST Framework Serialization POST is slow
This is what the debug toolbar shows:
My question is therefore, is there any way to run some kind of select_related on the validation? I've tried turning off validation but the toolbar still tells me that queries are being made.
These are my models:
class ActiveApartment(models.Model):
adress = models.CharField(default="", max_length=2000, primary_key=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
class Company(models.Model):
name = models.CharField(blank=True, null=True, max_length=150)
These are my serializers:
I have tried not using the explicit PrimaryKeyRelatedField as well, having validators as [] doesn't seem to stop the validation either for some reason.
class ActiveApartmentSerializer(serializers.ModelSerializer):
company = serializers.PrimaryKeyRelatedField(queryset=Company.objects.all())
class Meta:
model = ActiveApartment
list_serializer_class = ActiveApartmentListSerializer
fields = '__all__'
extra_kwargs = {
'company': {'validators': []},
}
class ActiveApartmentListSerializer(serializers.ListSerializer):
def create(self, validated_data):
data = [ActiveApartment(**item) for item in validated_data]
# Ignore conflcits is the only "original" part of this create method
return ActiveApartment.objects.bulk_create(data, ignore_conflicts=True)
def update(self, instance, validated_data):
pass
This is my view:
def post(self, request, format=None):
# Example of incoming data in request.data
dummydata = [{"company": 12, "adress": "randomdata1"}, {"company": 12, "adress": "randomdata2"}]
serializer = ActiveApartmentSerializer(data=request.data, many=True)
# This will run a query to check the validity of their foreign keys for each item in dummydata
if new_apartment_serializer.is_valid():
print("valid")
Any help would be appreciated (I would prefer not to use viewsets)
Have you tried to define company as IntegerField in the serializer, pass in the view's context the company IDs and add a validation method in field level?
class ActiveApartmentSerializer(serializers.ModelSerializer):
company = serializers.IntegerField(required=True)
...
def __init__(self, *args, **kwargs):
self.company_ids = kwargs.pop('company_ids', None)
super().__init__(*args, **kwargs)
def validate_company(self, company):
if company not in self.company_ids:
raise serializers.ValidationError('...')
return company
The way I solved it was I took the direction athanasp pointed me in and tweaked it a bit as his solution was close but not working entirely.
I:
Created a simple nested serializer
Used the init-method to make the query
Each item checks the list of companies in its own validate method
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = City
fields = ('name',)
class ListingSerializer(serializers.ModelSerializer):
# city = serializers.IntegerField(required=True, source="city_id") other alternative
city = CitySerializer()
def __init__(self, *args, **kwargs):
self.cities = City.objects.all()
super().__init__(*args, **kwargs)
def validate_city(self, value):
try:
city = next(item for item in self.cities if item.name == value['name'])
except StopIteration:
raise ValidationError('No city')
return city
And this is how the data looks like that should be added:
city":{"name":"stockhsdolm"}
Note that this method more or less works using the IntegerField() suggested by athan as well, the difference is I wanted to use a string when I post the item and not the primary key (e.g 1) which is used by default.
By using something like
city = serializers.IntegerField(required=True, source="city_id") works as well, just tweak the validate method, e.g fetching all the Id's from the model using values_list('id', flat=True) and then simply checking that list for each item.

Foreign Key - get values

I have created the following models. Many Accounts belong to one Computer.
class Computer(models.Model):
name = models.CharField(max_length=256)
def __str__(self):
return str(self.name)
class Accounts(models.Model):
computer = models.ForeignKey(Computer, on_delete=models.CASCADE, related_name='related_name_accounts')
username = models.CharField(max_length=10)
def __str__(self):
return str(self.username)
I now want to create a Form where i can choose one user from a dropdown-list of all users that belong to a Computer
class ComputerForm(forms.ModelForm):
class Meta:
model = Computer
exclude = ['name',]
def __init__(self, *args, **kwargs):
super(ComputerForm, self).__init__(*args, **kwargs)
self.fields['user']=forms.ModelChoiceField(queryset=Computer.related_name_accounts.all())
But i get an error
'ReverseManyToOneDescriptor' object has no attribute 'all'
How do i have to adjust the queryset in __init__ to display all users of a computer?
Edit:
>>> x = Computer.objects.filter(pk=3).get()
>>> x.related_name_accounts.all()
I would somehow need to pass the dynamic pk to the ModelForm?
Your issue is that you used a capital C in Computer.related_name_accounts.all(). This doesn't make sense, because the Computer model doesn't have related fields - it's instances do.
This line of code would work:
computer = Computer.objects.get(id=1)
computer.related_name_accounts.all()
But the issue is, I am not sure what you mean by ...of all users that belong to a Computer, since you don't have a ForeignKey or OneToOne relationship between User and Computer.
You can pass an instance of your model to the form like this:
x = Computer.objects.get(pk=3)
form = ComputerForm(instace=x)
And access it through self.instance in the form methods:
class ComputerForm(forms.ModelForm):
class Meta:
model = Computer
exclude = ['name',]
def __init__(self, *args, **kwargs):
super(ComputerForm, self).__init__(*args, **kwargs)
self.fields['user']=forms.ModelChoiceField(
queryset=self.instance.related_name_accounts.all()
)

Creating dynamic ModelChoiseField from users List objects

I have been trying for awhile now without any luck.. I have model Like this:
class List(models.Model):
name = models.CharField(max_length=100, default="")
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='lists')
def __str__(self):
returnself.name
class Meta:
unique_together = ['name', 'user']
Every user can create their own lists and add values to those lists. I have adding values and everything else working but to the form that adds these values I would somehow need to filter to show only users own lists, now its showing all lists created by every user... this is the form:
class data_form(forms.Form):
user_lists = List.objects.all()
selection = forms.ModelChoiceField(queryset=user_lists)
data = forms.IntegerField()
Any ideas how to filter it? I have tempoary "list.objects.all()" since dont want it to give error that crashes the server. I have watched a ton of examples on stackoverflow but none of them seems to be exact thing that I am looking for.. Thanks already for asnwers! :)
You need to get hold of the current user, e.g. like so or so.
That is, you pass request.user to the form when instantiating it in your view:
frm = DataForm(user=request.user)
In the __init__ of your form class, you can then assign the user-filtered queryset to your field:
class DataForm(forms.Form):
def __init__(self, *args, **kwargs):
user = kwargs.pop("user")
super(DataForm, self).__init__(*args, **kwargs)
self.fields['selection'].queryset = List.objects.filter(user=user)
You can set your form to take the user when initialized, and from there get a new queryset filtered by user.
class DataForm(forms.Form):
selection = forms.ModelChoiceField(queryset=List.objects.none())
data = forms.IntegerField()
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['selection'].queryset = List.objects.filter(user=user)
You would inititialize the form like this:
form = DataForm(request.user)

Django Inline Model Admin filter Foreign Field

I have a following problem.
I have 3 models:
class Deal(models.Model):
name = models.CharField(max_length=80)
class Site(models.Model):
name = models.CharField(max_length=80)
deal = models.ForeignKey(Deal)
class Picture(models.Model):
title = models.CharField(max_length=80)
deal = models.ForeignKey(Deal)
site = models.ForeignKey(Site)
I want to make Deal Admin with Site & Picture inline admin models:
class SiteInline(admin.StackedInline):
model = Site
extra = 1
class PictureInline(admin.StackedInline):
model = Picture
extra = 1
class DealAdmin(admin.ModelAdmin):
inlines = [
SiteInline,
PictureInline,
]
What I want to do is when I am selecting Site in Picture admin it shows only sites that I belong to the current Deal i am viewing (if im updating - not creating new one).
I want this to work in admin, I've spent many hours searching web but couldn't find anything useful, please help!
I was trying to do it this way, but I don't know how to access the parent model instance to get the deal id:
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
if db_field.name == 'site':
kwargs['queryset'] = Site.objects.filter(deal__id=1)
return super(PictureInline, self).formfield_for_foreignkey(db_field, request=None, **kwargs)
In DTing's variant I see the problem - self.instance.deal is setted in edit mode, but it unsetted in adding mode
I think, you should wrote
try:
self.fields['site'].queryset = Site.objects.filter(deal=self.instance.deal)
except:
self.fields['site'].queryset = Site.objects
instead
Django: accessing the model instance from within ModelAdmin?
class PictureInlineForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PictureInlineForm, self).__init__(*args, **kwargs)
self.fields['site'].queryset = Site.objects.filter(
deal=self.instance.deal)
class PictureInline(admin.ModelAdmin):
form = PictureInlineForm

Django, Filter the set presented in a many to many modelform by currently logged in user

I know it's there somewhere but I can't find it.
So I have a 'category' model and a 'book' model which has a many to many to 'category'. When creating a new book in a modelform, all the categories are presented to the user to assign to the book. In that case I want only the categories created by the current user to show up in that field, not all the categories.
What's the best approach?
Assuming your model like:
class Category(models.Model):
....
creator = models.ForeignKey(User)
class Book(models.Model):
...
categories = models.ManyToManyField(Category)
Assuming your form like:
class BookForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
current_user = kwargs.pop('user')
super(BookForm, self).__init__(*args, **kwargs)
self.fields['categories'].queryset = Categories.objects.filter(creator=current_user)
So, you need to overide __init__ of your form, pass the current user to this form. And then set a queryset attribute on the ManyToManyField you want.
Your view:
#GET request
book_form = BookForm(user=request.user)
#POST request
book_form = BookForm(data=request.POST, user=request.user)