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)
Related
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.
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()
)
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.
I am starting to work with django.
In my project I am using MPTT.
I have a fairly simple models.
Category: (the tree)
Product1: a reference and a foreign key to the category.
I added the root to the Category model, just to simplify the search for the base category for one specific product
models.py
class Category(MPTTModel):
name = models.CharField(max_length=200)
parent = TreeForeignKey('self', null=True, blank=True, related_name='children')
#property
def root(self):
return self.get_root().name
class Product1(models.Model):
ref = models.CharField(max_length=200, blank=True) #YVX2311
category = TreeForeignKey(Category)
admin.py
admin.site.register(Category, MPTTModelAdmin)
admin.site.register(Product)
What I would like is to modify the admin form, to be able to say something like.
models.ForeignKey(Category, limit_choices_to = {root : 'Some
Category for products1'})
Of course this is not possible, but it does illustrate what I want.
I want to be able to filter the TreeChoiseField to show only some branches based on a property on the model (root in this case).
Thanks
After much scrambling, I found a solution.
I do not think this is the best way to do it, but as far as I can see, it works.
I overloaded the mptt TreeNodeChoiceField and copied the TreeForeignKeyFiltered
In products, I changed the foreingkey for
category = TreeForeignKeyFiltered(Category, root = 'MotherBoard')
I added the root keyword to the TreeForeingKeyFiltered
class TreeForeignKeyFiltered(models.ForeignKey):
"""
Extends the foreign key, but uses mptt's ``TreeNodeChoiceField`` as
the default form field.
This is useful if you are creating models that need automatically
generated ModelForms to use the correct widgets.
"""
def __init__(self, *args, **kwargs):
self._root = kwargs.pop('root', None)
super(TreeForeignKeyFiltered, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
"""
Use MPTT's ``TreeNodeChoiceField``
"""
kwargs.setdefault('root', self._root)
kwargs.setdefault('form_class', TreeNodeChoiceFieldFiltered)
return super(TreeForeignKeyFiltered, self).formfield(**kwargs)
And finally I added the TreeNodeChoiceFieldFiltered as
class TreeNodeChoiceFieldFiltered(TreeNodeChoiceField):
def __init__(self, queryset, *args, **kwargs):
_root = kwargs.pop('root', None)
if _root is not None:
pid = None
for i in queryset:
if i.root.name == _root:
pid = i.tree_id
break
if pid is not None:
queryset = queryset.filter(tree_id = pid)
super(TreeNodeChoiceFieldFiltered, self).__init__(queryset, *args, **kwargs)
Now I can pass a keyword root to the foreingkey that will filter the posibilities in the admin form.
Again, I do not know if this is the best way to do it. Of course it can be cleaner....
I'm trying to set the value of a Django field inside of the Form class. Here is my model
class Workout(models.Model):
user = models.ForeignKey(User , db_column='userid')
datesubmitted = models.DateField()
workoutdate = models.DateField();
bodyweight = models.FloatField(null=True);
workoutname = models.CharField(max_length=250)
Here is the form class, in which i am attempting to achieve this:
class WorkoutForm(forms.ModelForm):
class Meta:
model = Workout
def __init__(self,*args, **kwargs):
# this is obviously wrong, I don't know what variable to set self.data to
self.datesubmitted = self.data['datesubmitted']
Ok, sorry guys. I'm passing the request.POST data to the WorkoutForm in my view like this
w = WorkoutForm(request.POST)
However, unfortunately the names of the html elements have different names then the names of the model. For instance, there is no date submitted field in the html. This is effectively a time stamp that is produced and saved in the database.
So I need to be able to save it inside the form class some how, I think.
That is why I am trying to set the datesubmitted field to datetime.datetime.now()
Basically I am using the form class to make the verification easier, and I AM NOT using the form for it's html output, which I completely disregard.
You have to do that in the save method of your form
class WorkoutForm(forms.ModelForm):
class Meta:
model = Workout
def __init__(self,*args, **kwargs):
super(WorkoutForm, self).__init__(*args, **kwargs)
def save(self, *args, **kw):
instance = super(WorkoutForm, self).save(commit=False)
instance.datesubmitted = datetime.now()
instance.save()
How ever you can set that in your model also to save the current datetime when ever a new object is created:
datesubmitted = models.DateTimeField(auto_now_add=True)
You can set some extra values set in form as:
form = WorkOutForm(curr_datetime = datetime.datetime.now(), request.POST) # passing datetime as a keyword argument
then in form get and set it:
def __init__(self,*args, **kwargs):
self.curr_datetime = kwargs.pop('curr_datetime')
super(WorkoutForm, self).__init__(*args, **kwargs)
You should not be using a ModelForm for this. Use a normal Form, and either in the view or in a method create a new model instance, copy the values, and return the model instance.