I have two models - Book and Tags connected by ManyToMany. After filtering the list of books, I want to get the list of tags used in these books and the count for each tag.
class Tag(models.Model):
name = models.CharField(max_length=255)
description = models.CharField(max_length=255)
type = models.CharField(max_length=255, default='tag')
is_showing = models.BooleanField()
using_count = models.IntegerField(default=0)
def __str__(self):
return self.name
class Meta:
constraints = [
models.UniqueConstraint(
fields=['name', 'type'], name='unique_migration_host_combination'
)
]
#property
#lru_cache
def count(self):
cnt = self.book_set.count()
return cnt
class Book(models.Model):
name = models.CharField(max_length=255)
tag = models.ManyToManyField(Tag)
def __str__(self):
return self.name
#property
def tags_count(self):
return self.tag.filter(type='tag').count()
#property
def genres_count(self):
return self.tag.filter(type='genre').count()
#property
def fandoms_count(self):
return self.tag.filter(type='fandom').count()
The last time I manually took out a list of tags from each book and put them in a dictionary, after which I got a dictionary of the number of tags used, which was not very convenient to use in the django template engine
Related
I am trying to test my Models Project, Category and Tag. I'm running into an issue when trying to add tags to my project model.
It won't allow me to do it in the Project model itself for eg.
self.project = Project.objects.create(
...
tags=Tag.objects.create("HTML5"),
)
Django docs suggest the I do it as below. However I can't "add" the Tag without saving the model and I can't save the model without adding the Tag
Tests
class ProjectTests(TestCase):
def setUp(self):
self.tag = Tag.objects.create(name="HTML5")
self.project = Project(
title="Oaks on Main Shopping Center",
url="www.oaksonmain.co.za",
image=SimpleUploadedFile(
name="test-image.jpg",
content=open(
"static\\images\\test_images\\florian-olivo-4hbJ-eymZ1o-unsplash (1).jpg", "rb"
).read(),
content_type="image/jpeg",
),
description="Beautiful website created for Oaks on Main Shopping Center in Knysna!",
category=Category.objects.create(name="Website"),
)
self.project.save() <- Problem here
self.project.tags.add(self.tag) <- Problem here
def test_project_model(self):
self.assertEqual(f"{self.project.title}", "Oaks on Main Shopping Center")
self.assertEqual(f"{self.project.url}", "www.oaksonmain.co.za")
self.assertEqual(
f"{self.project.description}",
"Beautiful website created for Oaks on Main Shopping Center in Knysna!",
)
self.assertEqual(self.tags.count(), 3)
self.assertEqual(self.category.count(), 1)
self.assertEqual(self.image.count(), 1)
def test_project_listview(self):
resp = self.client.get(reverse("index"))
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, self.project.title)
self.assertTemplateUsed(resp, "page/index.html")
Models
class Project(models.Model):
class Meta:
ordering = ["-id"] # Always show latest projects first
verbose_name_plural = "Projects"
title = models.CharField(max_length=50)
url = models.URLField()
image = models.ImageField(upload_to=f"{title}/")
description = models.TextField()
category = models.ForeignKey("Category", on_delete=models.PROTECT, related_name="categories")
tags = models.ManyToManyField("Tag", verbose_name="tags")
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse("index")
class Category(models.Model):
class Meta:
ordering = ["name"]
verbose_name_plural = "Categories"
name = models.CharField(max_length=20)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("index")
class Tag(models.Model):
class Meta:
ordering = ["name"]
verbose_name_plural = "Tags"
name = models.CharField(max_length=10)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse("index")
You can first save the tag or the model object, and then later you link the tag as (one of) tags of that Project, so:
html5_tag, __ = Tag.objects.get_or_create(name='HTML5')
self.project = Project.objects.create(
# no tags=…
)
self.project.tags.add(html5_tag)
Your ImageField also should work with a callable for the upload_to=… parameter, so:
class Project(models.Model):
# …
def upload_image(self, filename):
return f'{self.title}/{filename}'
image = models.ImageField(upload_to=upload_image)
# …
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!
I'd like to get all Profile where tag='hello' and tag='world'. I tried with Q() query but I don't have the correct result.
models.py
class Tag(models.Model):
name = models.CharField(unique=True, max_length=100)
slug = models.SlugField(unique=True, max_length=100)
def __str__(self):
return self.name
class Meta:
ordering = ['slug']
class Profile(models.Model):
name = models.CharField(max_length=100)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.name
class Meta:
ordering = ['name']
views.py
def search(request: HttpRequest):
q_tag_list = request.GET.get('search-tag').split(',')
profile_filter = Q()
for tag in q_tag_list:
profile_filter = profile_filter & Q(tags__slug__startswith=tag)
profiles = Profile.objects.filter(profile_filter)
return render(request, 'list.html', {'profiles': profiles})
sql generated (from django debug toolbar)
SELECT "socialmedia_profile"."id", "socialmedia_profile"."name", "socialmedia_profile"."facebook_name", "socialmedia_profile"."facebook_latest_likes" FROM "socialmedia_profile" INNER JOIN "socialmedia_profile_tags" ON ("socialmedia_profile"."id" = "socialmedia_profile_tags"."profile_id") INNER JOIN "socialmedia_tag" ON ("socialmedia_profile_tags"."tag_id" = "socialmedia_tag"."id") WHERE ("socialmedia_tag"."slug" LIKE '''hello%''' ESCAPE '\' AND "socialmedia_tag"."slug" LIKE '''world%''' ESCAPE '\') ORDER BY "socialmedia_profile"."name" ASC
I am trying to create a product filter.
I am sending the user choice in URL
if the user select size = L then using request.GET
I am receiving:
{'size': ['L']}
But I want to receive: {'size':{'op':'in','attri':'L'}}
Is this possible?
Please help
my models are
class ProductAttribute(models.Model):
slug = models.SlugField(max_length=50, unique=True)
name = models.CharField(max_length=100)
op = models.CharField(max_length=20,default='in')
class Meta:
ordering = ('slug', )
def __str__(self):
return self.name
def get_formfield_name(self):
return slugify('attribute-%s' % self.slug, allow_unicode=True)
def has_values(self):
return self.values.exists()
class AttributeChoiceValue(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(max_length=100)
attribute = models.ForeignKey(
ProductAttribute, related_name='values', on_delete=models.CASCADE)
class Meta:
unique_together = ('name', 'attribute')
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=128)
attributes = HStoreField(default={})
q2 = AttributeChoiceValue.objects.filter(attribute__name='size')
My size filter(filter.py) is:
size = django_filters.ModelMultipleChoiceFilter(queryset=q2.values_list('name', flat=True).distinct(),widget=forms.CheckboxSelectMultiple)
I am currently using the following query to filter my database in views.py
result = Product.objects.all()
for key, value in request.GET:result = result.filter(**{'attributes__{}__in'.format(key): value})
I want to make it
a=request.GET
for key, value in a:
result = result.filter(**{'attributes__{}__{}'.format(key,a['op']): value})
so that if I even use Price range as filter my query filter accordingly will be
attributes__price__range
You can send info to your views via "path converters":
https://docs.djangoproject.com/en/2.0/topics/http/urls/#path-converters
Or using regular expressions:
https://docs.djangoproject.com/en/2.0/topics/http/urls/#using-regular-expressions
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.