Many DB queries for string representation of model object - django

I have this models:
class Country(models.Model):
name = models.CharField(max_length=250)
def __str__(self):
return str(self.name)
class City(models.Model):
name = models.CharField(max_length=250)
country = models.ForeignKey(Country, default=None, blank=True)
def __str__(self):
return str(self.name)
class Airport(models.Model):
name = models.CharField(max_length=250)
city = models.ForeignKey(City, default=None, blank=True)
def __str__(self):
return "{0} - {1} - {2}".format(self.city, self.city.country, self.name)
class Tour(models.Model):
title = models.CharField(max_length=200)
tour_from = models.ForeignKey(Airport)
tour_to = models.ForeignKey(Airport)
def __str__(self):
return str(self.title)
For string representation of Airport Django sends many requests to DB:
302.06 ms (591 queries including 586 similar and 586 duplicates )
Queries screenshot:
At tour/create page I have a ModelForm for creating a tour and Django sends these queries for displaying form.
forms.py:
class TourCreateForm(forms.ModelForm):
class Meta:
model = Tour
fields = ['title', 'tour_from', 'tour_to']
views.py:
class DashboardTourCreate(CreateView):
model = Tour
template_name = "dashboard/tour/create.html"
form_class = TourCreateForm
def get_context_data(self, **kwargs):
context = super(DashboardTourCreate, self).get_context_data(**kwargs)
context['page_name'] = ['tour', 'tour-index']
context['page_title'] = "Create Tour"
return context
How I can reduce queries count?

Root Cause
def __str__(self):
return "{0} - {1} - {2}".format(self.city, self.city.country, self.name)
When the tour_to and tour_from fields are rendered as <option> in the <select> widget the Airport.__str__ method is called. Because Airport.__str__ has self.city.county and both of these are ForeignKey's, the Django ORM issues a query to grab the airports city and the citys country.
And it does this for every single Airport that is an <option> which means the problem will get progressively worse the more Airport's that are added.
Solution
Leverage select_related[1]. select_related will tell the Django ORM to pull in the related fields ('city', 'county') whenever it grabs an Airport.
class TourCreateForm(forms.ModelForm):
class Meta:
model = Tour
fields = ['title', 'tour_from', 'tour_to']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['tour_from'].queryset = Airport.objects.select_related(
'city__country',
)
self.fields['tour_to'].queryset = Airport.objects.select_related(
'city__country',
)
[1] https://docs.djangoproject.com/en/2.1/ref/models/querysets/#select-related

As f-string is a string literal expressions evaluated at run time link, this might be faster that other string format but i am not fully sure. I am expecting following modification may reduce the over all time.
class Airport(models.Model):
name = models.CharField(max_length=250)
city = models.ForeignKey(City, default=None, blank=True)
def __str__(self):
return f"{self.city} - {self.city.country} - {self.name}"

I fix this issue by adding Queryset to forms.py:
class TourCreateForm(BaseForm):
airports = Airport.objects.select_related('city', 'city__country').all()
tour_from = forms.ModelChoiceField(queryset=airports)
tour_to = forms.ModelChoiceField(queryset=airports)
But I think this is not correct!

Related

Querying other Model in class-based view produces error

I have two tables, in one of which the possible items with their properties are recorded, in the other the stock levels of these respective items are recorded.
class itemtype(models.Model):
item_id = models.IntegerField(primary_key=True)
item_name = models.CharField(max_length=100)
group_name = models.CharField(max_length=100)
category_name = models.CharField(max_length=100)
mass = models.FloatField()
volume = models.FloatField()
packaged_volume = models.FloatField(null=True)
used_in_storage = models.BooleanField(default=False, null=True)
class Meta:
indexes = [
models.Index(fields=['item_id'])
]
def __str__(self):
return '{}, {}'.format(self.item_id, self.item_name)
class material_storage(models.Model):
storage_id = models.AutoField(primary_key=True)
material = models.ForeignKey(itemtype, on_delete=models.PROTECT)
amount_total = models.IntegerField(null=True)
price_avg = models.FloatField(null=True)
amount = models.IntegerField(null=True)
price = models.FloatField(null=True)
timestamp = models.DateTimeField(default=timezone.now)
def __str__(self):
return '{}, {} avg.: {} ISK'.format(self.material, self.amount, self.price)
I have a ModelForm based on the table material_storage, in which a checkbox indicates whether transport costs should be included or not.
In the form_valid() method of this ModelForm class the calculations are performed. To do so, I have to retrieve the volume per unit of the given item to use it for my transport cost calculations. Trying to geht that value the way shown below leads to an error I don't really understand.
class MaterialChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
return obj.item_name
class NewAssetForm(forms.ModelForm):
material = MaterialChoiceField(models.itemtype.objects.filter(used_in_storage= True))
needs_transport = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
super(NewAssetForm, self).__init__(*args, **kwargs)
self.fields['amount'].widget.attrs['min'] = 1
self.fields['price'].widget.attrs['min'] = 0.00
class Meta:
model = models.material_storage
fields = (
'material',
'amount',
'price',
)
widgets = {
'material': forms.Select(),
}
class NewItemView(FormView):
template_name = 'assetmanager/newasset.html'
form_class = forms.NewAssetForm
success_url = '/storage/current'
def form_valid(self, form):
unit_volume = itemtype.objects.values('packaged_volume').filter(item_id=form.cleaned_data['material'])[0]['packaged_volume']
I believe that this has something to do with querying a different model than specified in the form, but I don't understand what exactly is the problem. Especially the fact, that running the exact same query in the django shell returns the correct value does not really help to understand what is going wrong here. Could somebody please tell me how to get the desired value the correct way?
Change last line from:
unit_volume = itemtype.objects.values('packaged_volume').filter(item_id=form.cleaned_data['material'])[0]['packaged_volume']
to:
unit_volume = itemtype.objects.values('packaged_volume').filter(item_id=form.cleaned_data['material'].item_id)[0]['packaged_volume']
The error says, you are giving Item instance to the query, where is item_id asked.

Django: How to check a Form with a m2m relation object already exists or is “unique_together”?

I am testing forms and nesting models in django. In my Project a Person can enter departure, arrival (city names) and choose a weekly day (Mon-Fri). Maybe he drives every “Tuesday” from Amsterdam to Paris. I wanted this constellation to be unique – just for fun. So If another user enters the same route the relation should be linked to the same Car.object.
Models.py
class Person(models.Model):
name = models.CharField(max_length=255, blank=False, unique=True)
route = models.ManyToManyField('Car')
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=255, blank=False, unique=True)
weekdays = models.ForeignKey('Week', null=True, blank=False, on_delete=models.SET_NULL)
departure = models.CharField(max_length=255, blank=False)
arrival = models.CharField(max_length=255, blank=False)
class Meta:
unique_together = ['weekdays', 'departure', 'arrival'] # --- Unique combination
def __str__(self):
return self.name
class Week(models.Model):
day = models.CharField(max_length=255, blank=False, unique=True)
def __str__(self):
return self.day
views.py
class RouteCreateView(CreateView):
model = Person
template_name ="testa/create_route.html"
form_class = RouteForm
success_url = reverse_lazy('testa:testa_home')
def form_valid(self, form):
return super().form_valid(form)
forms.py
class RouteForm(forms.ModelForm):
# --- apply ChoiceField
day = forms.ModelChoiceField(queryset=None)
car_name = forms.CharField()
departure = forms.CharField()
arrival = forms.CharField()
class Meta:
model = Person
fields = [
'name'
]
def __init__(self, *args, **kwargs):
super(RouteForm, self).__init__(*args, **kwargs)
self.fields['day'].queryset = Week.objects.all()
def save(self, commit=True):
personData = super().save(commit)
data = self.cleaned_data
carData = Car(name=data['car_name'], weekdays=data['day'], departure=data['departure'], arrival=data['arrival'])
if commit:
carData.save()
personData.route.add(carData) # --- save m2m relation
return personData
If i enter two times for example „“Tuesday” from Amsterdam to Paris “ then an Error Message appears obviously, this error message (it´s german), telling me I have a double entry / Key.
Question
So my save()Method does not work because I need some kind of logic, so that Django takes the existing car.object or creates a new - if it is not a double entry. But I do not know where to start? The easiest way would be to get some kind of response from my model meta option Car.unique_together so "if it´s an “double-key error” then take the existing object". Is there a way to fetch the response? And what kind of Values it would be, only errors, could not find any hint in the doc? Or should I try some logic with exists()
That was my kind of idea / approach of a new save() 😊
def save(self, commit=True):
personData = super().save(commit)
data = self.cleaned_data
carData = Car(name=data['car_name'], weekdays=data['day'], departure=data['departure'], arrival=data['arrival'])
if commit:
# Check if database sends unique_together response
# if yes
if Car.Meta.unique_together is True:
getAlternative = Car.object.get(Meta.unique_together) # --- get the object which already exist
personData.route.add(getAlternative) # --- save m2m relation
# if not
else:
carData.save() # --- save object
personData.route.add(carData) # --- save m2m relation
return personData
obviously i get a error message: type object 'Car' has no attribute
'Meta'
Theres get_or_create for such use case: https://docs.djangoproject.com/en/2.2/ref/models/querysets/#get-or-create
...
car, created = Car.objects.get_or_create(
weekdays=data['day'],
departure=data['departure'],
arrival=data['arrival'],
defaults = dict(name=data['car_name']),
)
personData.route.add(car)
...
Obviously given name gets ignored if another car with same weekdas, departure, arrival has been found.
I suggest to put the code for creating the car and adding the route in a transaction.atomic() https://docs.djangoproject.com/en/2.2/topics/db/transactions/#django.db.transaction.atomic

How to apply autocomplete in manytomany field in django

I would like to apply the autocomplete option for my Preorder.preorder_has_products.through model in order to be able to load products with autocomplete(i tried unsuccessfully).Moreover, I have an inline implementation in order to be able to select for one preorder more than one product. The obstacle is that I have a manytomany field(preorder_has_products) as you can see below and I do not know how to implement the autocomplete.
models.py
class Preorder(models.Model):
client = models.ForeignKey(Client,verbose_name=u'Πελάτης')
preorder_date = models.DateField("Ημ/νία Προπαραγγελίας",null=True, blank=True, default=datetime.date.today)
notes = models.CharField(max_length=100, null=True, blank=True, verbose_name="Σημειώσεις")
preorder_has_products=models.ManyToManyField(Product,blank=True)
def get_absolute_url(self):
return reverse('preorder_edit', kwargs={'pk': self.pk})
class Product(models.Model):
name = models.CharField("Όνομα",max_length=200)
price = models.DecimalField("Τιμή", max_digits=7, decimal_places=2, default=0)
barcode = models.CharField(max_length=16, blank=True, default="")
eopyy = models.CharField("Κωδικός ΕΟΠΥΥ",max_length=10, blank=True, default="")
fpa = models.ForeignKey(FPA, null=True, blank=True, verbose_name=u'Κλίμακα ΦΠΑ')
forms.py
class PreorderHasProductsForm(ModelForm):
product = ModelChoiceField(required=True,queryset=Product.objects.all(),widget=autocomplete.ModelSelect2(url='name-autocomplete'))
class Meta:
model=Preorder.preorder_has_products.through
exclude=('client',)
def __init__(self, *args, **kwargs):
super(PreorderHasProductsForm, self).__init__(*args, **kwargs)
self.fields['product'].label = "Ονομα Προϊόντος"
PreorderProductFormSet = inlineformset_factory(Preorder,Preorder.preorder_has_products.through,form=PreorderHasProductsForm,extra=1)
my views.py for autocomplete
class NameAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
# Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated():
return Product.objects.none()
qs = Product.objects.all()
if self.q:
qs = qs.filter(product__istartswith=self.q)
return qs
my template is written based on this tutorial : https://django-autocomplete-light.readthedocs.io/en/master/tutorial.html
and finally my url for autocomplete:
url(r'^name-autocomplete/$',views.NameAutocomplete.as_view(),name='name-autocomplete'),
My result based on the above snippets is depicted in the attached image.
what could be wrong? I guess one possible issue could be the reference to the manytomanyfield table. Any idea?

Add some parameters to FilterSet (django-filter) + some parameters

i have 3 models:
class Category(models.Model):
name = models.CharField(max_length=200)
slug = models.SlugField(max_length=70, null=True, blank=True)
class SubCategory(models.Model):
category= models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=200, )
class Products(models.Model):
user= models.ForeignKey(User, on_delete=models.CASCADE)
category= models.ForeignKey(Category, on_delete=models.CASCADE)
subcategory = models.CharField(max_length=200, null=True, blank=True)
and i have a view which receive request and category.slug
def category_list(request, slug):
category = Category.objects.get(slug=slug)
products = ProductFilter(request.GET, queryset=Products.objects.filter(category=category)
return render(request, 'products/category_list.html', {"products":products, 'category': category})
when rendering i receive a QuerySet filtered to Category
I want to send category.id to ProductsFilter and recive a dynamic Choices from database
class ProductsFilter(django_filters.FilterSet):
subcategory= django_filters.ChoiceFilter(lookup_expr='iexact', choices=TEST, required=False)
class Meta:
model = Products
fields = {
"subcategory",
}
Want to change choices=TEST to choices=list(SubCategory.objects.filter(category_id=category.id)
Is this possible?
The answer from #Sherpa has just two slight problems. First, you should replace fields with filters. Second, you can't use += operator, you have to directly assign to the filter's extra.
Here's my working code in two different ways
class LayoutFilterView(filters.FilterSet):
supplier = filters.ChoiceFilter(
label=_('Supplier'), empty_label=_("All Suppliers"),)
def __init__(self, *args, **kwargs):
super(LayoutFilterView, self).__init__(*args, **kwargs)
# First Method
self.filters['supplier'].extra['choices'] = [
(supplier.id, supplier.name) for supplier in ourSuppliers(request=self.request)
]
# Second Method
self.filters['supplier'].extra.update({
'choices': [(supplier.id, supplier.name) for supplier in ourSuppliers(request=self.request)]
})
Originally posted here
You can handle this in the FilterSet.__init__ method. Something like the below (Note that I haven't tested it, may require some fiddling):
class ProductsFilter(django_filters.FilterSet):
subcategory= django_filters.ChoiceFilter(lookup_expr='iexact', choices=[], required=False)
def __init__(self, category, *args, **kwargs):
super(ProductsFilter, self).__init__(*args, **kwargs)
choices = self.fields['subcategory'].extra['choices']
choices += [
(subcat.name, subcat.name) for subcat
in SubCategory.objects.filter(category=category)
]
class Meta:
model = Products

django-piston: how to get values of a many to many field?

I have a model with ManyToManyField to another model. I would like to get all the info on a particular record (including the related info from other models) return by JSON.
How to get django-piston to display those values? I would be happy with just primary keys.
Or can you suggest another option ?
I may be wrong, but this should do it:
class PersonHandler(BaseHandler):
model = Person
fields = ('id', ('friends', ('id', 'name')), 'name')
def read(self, request):
return Person.objects.filter(...)
You need to define a classmethod on the handler that returns the many-to-many data, I don't believe Piston does this automatically.
class MyHandler(BaseHandler):
model = MyModel
fields = ('myfield', 'mymanytomanyfield')
#classmethod
def mymanytomanyfield(cls, myinstance):
return myinstance.mymanytomanyfield.all()
My code:
Models:
class Tag(models.Model):
"""docstring for Tags"""
tag_name = models.CharField(max_length=20, blank=True)
create_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return self.tag_name
class Author(models.Model):
"""docstring for Author"""
name = models.CharField(max_length=30)
email = models.EmailField(blank=True)
website = models.URLField(blank=True)
def __unicode__(self):
return u'%s' % (self.name)
class Blog(models.Model):
"""docstring for Blogs"""
caption = models.CharField(max_length=50)
author = models.ForeignKey(Author)
tags = models.ManyToManyField(Tag, blank=True)
content = models.TextField()
publish_time = models.DateTimeField(auto_now_add=True)
update_time = models.DateTimeField(auto_now=True)
def __unicode__(self):
return u'%s %s %s' % (self.caption, self.author, self.publish_time)
Handle:
class BlogAndTagsHandler(BaseHandler):
allowed_methods = ('GET',)
model = Blog
fields = ('id' 'caption', 'author',('tags',('id', 'tag_name')), 'content', 'publish_time', 'update_time')
def read(self, request, _id=None):
"""
Returns a single post if `blogpost_id` is given,
otherwise a subset.
"""
base = Blog.objects
if _id:
return base.get(id=_id)
else:
return base.all() # Or base.filter(...)
Works petty good.