Best practice for joining MySQL tables in Django - python

I have two models like this in my Django project.
class Product(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE)
sub_category = models.ForeignKey(ProductSubCategory, on_delete=models.CASCADE)
comment = models.TextField()
size = models.CharField(max_length=60)
price = models.FloatField(default=0)
class ProductImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
alt = models.CharField(max_length=200)
picture = models.FileField()
Of course a product can have 2 or more pictures.
How can I get a all products with all related every single image in my view and pass the result as a context to the template?
I searched and tried these:
prefetch_related,
select_related,
raw sql query
and couple of suggested ways, but cannot get the result.

You may use prefetch_related to optimise the query
Product.objects.filter(sub_category = sub_category_id).prefetch_related('productimage_set')
Then, in the template
{% for image in product.productimage_set %}
display image here
{% endfor %}
You may alternately set a related_name in the foreign-key, like
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="product_images")
and then in the view,
Product.objects.filter(sub_category = sub_category_id).prefetch_related('product_images')
end then in the template
{% for image in product.product_images %}
display image here
{% endfor %}
Refer ForeignKey.related_name

Related

django - having trouble selecting values based on FK relationship in views.py

I have two models that look like;
class Body(models.Model):
body_id = models.AutoField(primary_key=True)
is_adult = models.BooleanField(default=False)
body = models.TextField()
add_user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
add_date = models.DateTimeField()
edit_user = models.CharField(max_length=25, blank=True, null=True)
edit_date = models.DateTimeField(blank=True, null=True)
category = models.ForeignKey(Category, models.CASCADE)
class Meta:
managed = True
db_table = 'jdb_body'
class BodyTag(models.Model):
body_tag_id = models.AutoField(primary_key=True)
body = models.ForeignKey('Body', models.CASCADE)
tag = models.ForeignKey('Tag', models.CASCADE, db_column='tag')
class Meta:
managed = True
db_table = 'jdb_body_tag'
def __str__(self):
return self.tag
I have a view that looks like;
def index(request):
latest_body_list = Body.objects.all().order_by('-body_id')
context = {
'latest_body_list': latest_body_list
}
return render(request, 'index.html', context)
That view gives me a list Body records no problem. I am trying to display Body records with their corresponding BodyTag records. What am I doing wrong?
You neeed a ManyToManyField in your class Body
tags = models.ManyToManyField('Tag')
To access
body = Body.objects.get(body_id=1)
tags = body.tags.all()
EDIT
My previous answer was incorrect because I did not see the more complex relationship. The problem is this: You have many BodyTag objects to one Body object, but you also have many BodyTag objects to one Tag object. So as shown in the image, BodyTag's BT4 and BT5 belong to Body's BODY1 and BODY2. That's why #chipchap31 is talking of a ManyToMany relationship.
If this is not the relationship you want, and from your comments I do not think that you want a ManyToMany field, then the BodyTag model should be either changed, or perhaps discarded. Perhaps relate Body to Tag directly using the ForeignKey, and then have a field in the Tag model that distinguishes it with the type of tag it is, so one type would be body, and you can use a choices field to show all the different types of Tags.
Previous answer (Incorrect)
If you mean displaying these in your template, then all you have to do is follow the ForeignKey relationship backwards. This is shown in the documentaion for the views, but it would look pretty much the same in the template. Something like:
{% for body in latest_body_list %}
{{ body }}
{% for tag in body.tag_set.all %}
{{ tag }}
{% endfor %}
{% endfor %}
The -set is what tells django to look backward in the ForeignKey relationship.
A perhaps better way, also shown in the documentation would be to define a related_name in your ForeignKey:
class BodyTag(models.Model):
body_tag_id = models.AutoField(primary_key=True)
body = models.ForeignKey('Body', models.CASCADE, related_name='tags')
tag = models.ForeignKey('Tag', models.CASCADE, db_column='tag')
Then your template could be written a little better:
{% for body in latest_body_list %}
{{ body }}
{% for tag in body.tags.all %}
{{ tag }}
{% endfor %}
{% endfor %}

Django models referencing each other

I have two models and I want to put an if condition in my template to get the desired output.
I have the first model
class Subject(models.Model):
name = models.CharField(max_length=50)
books = models.ForeignKey('Books')
And the second one
class Books(models.Model):
name = models.CharField(max_length=50)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
And a view
def View(request):
subjects = Subject.objects.all()
return render('my_html.html",{'subjects':subjects})
In my model, I have wanted to do this.
{% for subject in subjects %}
{% if subject.books.count > 0 %}
{{ subject.name }}
{% endif %}
{% endfor %}
I thought the only way is to have the models referencing each other for my plan to work but I also think that looping will require more resources. Is there a better way of doing this either thro' the view or just in the models?
You only need to ForeignKey from Book to Subject, so:
class Subject(models.Model):
name = models.CharField(max_length=50)
# no books
class Books(models.Model):
name = models.CharField(max_length=50)
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
then you can filter the subjects such that it only retrieves Subjects with at least one book:
def view(request):
subjects = Subject.objects.filter(books__isnull=False).distinct()
return render(request, 'my_html.html', {'subjects': subjects})
this will make a LEFT OUTER JOIN on the table of the Book model, and check if there is at least one book. You thus do not need to filter in the template (which is not a good idea anyway).

How can i make good models for categories in django?

im trying to make an online menu. i want to make a category, sub category and items that goes in one of those.
I mean, an item can be on a category like "sushi" -> nameofsushi, or be in a sub category like sushi -> avocado rolls -> nameofsushi.
i have something like this in my models.py but is there a better way to do it?
class Category(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(max_length=500, verbose_name='DescripciĆ³n', blank=True, null=True)
parent = models.ForeignKey('self', related_name='children', null=True, blank=True, on_delete=models.CASCADE)
def __str__(self):
return 'Category: {}'.format(self.name)
class MenuItem(models.Model):
name = models.CharField(max_length=200)
image = models.ImageField(upload_to='menu_images/', null=True, blank=True)
description = models.TextField(blank=True, null=True)
price = models.DecimalField(max_digits=6, decimal_places=0)
other_Price = models.DecimalField(max_digits=6, decimal_places=0, null=True, blank=True)
categories = models.ManyToManyField('Category', related_name='item', null=True, blank=True)
def __str__(self):
return 'MenuItem: {}'.format(self.name)
You need to carefully think about your model design. The way you have designed categories allows you to have unlimited sub-categories. Do you really need that flexibility? Because it comes at the cost of complexity. Think about how you will be interacting with the models.
With your current design, it would be challenging to render in a Django template without preprocessing it in Python using some gnarly recursion, because you have no way apriori to know how many nested sub categories you have. You could have sub-category, sub-sub-category, sub-sub-sub-category and so on.
Also, querying your models will be complicated. Say you have 'Cat1'->'Sub-cat1'->'Sub-sub-cat1'->'menuitem1'. How do you find all menu items that are a descendant of 'sub-cat1'? All I can think of is MenuItem.objects.filter(parent__parent=subcat1_obj). Not very Pythonic or clear to a reader. And you run into problems as you dont know how many layers of sub-categories you have.
Or how do you get just the menu categories? Category.objects.filter(parent=None). Its not obvious from this code what we are talking about.
I would, if your use-case allows it, simplify your model design in this way:
class MenuCategory(models.Model):
category = models.ForeignKey(Category, ...)
...
class MenuSubCategory(models.Model):
menu_category = models.ForeignKey(MenuCategory, ...)
...
class MenuItem(models.Model):
menu_categories = models.ManyToManyField(MenuCategory, ...)
menu_subcategories = models.ManyToManyField(MenuSubCategory, ...)
...
Now rendering your models in a template would be straight forward (given context['menu_categories'] = MenuCategory.objects.all()):
{% for cat in menu_categories %}
{% for item in cat.menuitem_set.all %}
{{ item }}
{% endfor %}
{% for subcat in cat.menusubcategory_set.all %}
{% for item in subcat.menuitem_set.all %}
{{ item }}
{% endfor %}
{% endfor %}
{% endfor %}
Now querying your models will also be more clear. You could also if needed add a Menu model and have different menus.

Context `Order.Items` not appearing in PDF file from Django Admin

Hellooo,
I am trying to add a feature to my admin where I can download order details from a PDF file, which has gone successful so far except that Order.Model is not completely appearing.
so I have 3 models: Item, OrderItem and Order. The Order has a Many-to-Many relationship with OrderItem and the OrderItem has a Foreign Key with Item.
In the template I am trying to loop between the Order.Items which is items = models.ManyToManyField(OrderItem) but it is not rendering any data.
Here is the models.py
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.FloatField()
class OrderItem(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
ordered = models.BooleanField(default=False)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
variation = models.ManyToManyField(Variation)
class Order(models.Model):
items = models.ManyToManyField(OrderItem)
Here is the views.py
#staff_member_required
def admin_order_pdf(request, order_id):
order = get_object_or_404(Order, id=order_id)
html = render_to_string('pdf.html', {'order': order})
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'filename="order_{}.pdf"'.format(Order.id)
weasyprint.HTML(string=html).write_pdf(response)
return response
here is the url.py
path('admin/order/(<order_id>\d+)/pdf/', views.admin_order_pdf, name='admin_order_pdf')
Here is the pdf.html template which is only showing as highlighted
Ordered on: {{order.ordered_date}} <----------Showing
{% for order_item in order.items.all %}
{{ order_item.item.title }} <----------Not Showing
{% endfor %}
I even tried removing the forloop but still nothing happened
Ordered on: {{order.ordered_date}} <----------Showing
{{ order_item.item.title }} <----------Not Showing
I think I dont have enough information to answer, but from what I see here, you are only passing an order from the view (single order) and not a queryset or any other iterable to the template. Am I missing the queryset or iterable?
If you want to access other objects related to the Order (such as OrderItem) you are missing it in the template. From you models I can see that Order has a relationship with OrderItem and not Items. Items are the one you try to access in the view. (Do you even have an Items model?)
Removing the loop wont work, as there is no order_item variable available in the template.
You are using manytomany field with OrderItem. Hence in order to reach OrderItem, you will have to go via a through model which is created under the hood. You are accessing the through mode when writing order.items.all() instead of your OrderItem model. For further information see django docs here: https://docs.djangoproject.com/en/3.1/topics/db/examples/many_to_many/
Assuming you intention here is to create a manytomany relation between Order and Item models. Then here is how you should create your ManyToMany
class Item(models.Model):
title = models.CharField(max_length=100)
price = models.FloatField()
class OrderItem(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
ordered = models.BooleanField(default=False)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
quantity = models.IntegerField(default=1)
variation = models.ManyToManyField(Variation)
class Order(models.Model):
items = models.ManyToManyField(Item, through='OrderItem')
This will resolve your problem.
#a-k First of all {{order.ordered_date}} this is not valid. Your Order model doesn't have any field or method ordered_date according to what you've shown here.
Ordered on: {{order.ordered_date}} # This is wrong and invalid
{% for order_item in order.items.all %}
{{ order_item.item.title }} # This is valid, use this method to render other fields
{% endfor %}
When you convert your above html code, it will generate pdf like this. item_1 here is the title of item. {{order.ordered_date}} didn't worked instead {{ order_item.item.title }} worked and it was renderd in the pdf.
The second snippet which is shared by you is invalid and will not show any dynamic data. It will just render Ordered on:
Ordered on: {{order.ordered_date}} <----------Invalid
{{ order_item.item.title }} <----------Invalid
I hope this resolves your queries.

How to add tags using taggit in Django admin

I am trying to add tags to my blog app using django admin but every-time I add a tag through the admin console, I get this error Can't call similar_objects with a non-instance manager
Is this because I am not saving the the tags in the admin or is this because I implemented taggit wrongly?
This is where I define my view and display and article, I try to grab similar articles using taggit
def article(request, slug):
article = Post.objects.filter(slug=slug).values()
article_info = {
"articles": article,
"related_articles" : Post.tags.similar_objects()
}
return render(request, 'article.htm', article_info)
Update
This is how my post model looks like
STATUS = (
(0,"Draft"),
(1,"Publish")
)
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
author = models.CharField(max_length=200, unique=True)
author_biography = models.TextField(default="N/A")
updated_on = models.DateTimeField(auto_now= True)
content = models.TextField()
upload_image = models.ImageField(default="default.png", blank=True)
created_on = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=STATUS, default=0)
tags = TaggableManager()
class Meta:
ordering = ['-created_on']
def __str__(self):
return self.title
Right, I see the problem.
You are trying to access tags on the class Post and not an instance of Post. However, you're also using filter() and values() and the variable name is singular, so I think there's perhaps a misunderstanding there as well.
I assume the kind of thing you want to do is this;
def article(request, slug):
article = Post.objects.get(slug=slug)
article_info = {
"article": article,
"related_articles" : article.tags.similar_objects() # Instance of Post is article
}
return render(request, 'article.htm', article_info)
Usually, this type of structure is made using ManyToMany relationships.
Going this way, your code should look like this:
class Tag(models.Model):
name = models.CharField(max_length=255)
class Post(models.Model):
title = models.CharField(max_length=200, unique=True)
tag = models.ManyToManyField(Tag)
To add a tag to a post:
post_obj = Post.objects.get(title="le title")
tag = Tag.objects.get(name="le name")
post_obj.tag.add(tag)
To query:
post_obj = Post.objects.get(title="le title")
tags_in_post = post_obj.tag.all()
This should facilitate your page rendering. Assuming you want to display every post and, within it, every that, your code should be something like this:
// in the end of the view function:
return render(request, 'article.htm', context={'post': article_info})
// inside the template:
{% for p in post %}
<li> <span> {{p.title}} </span>
{% for tag in post.tag %}
<ul> {{tag.name}} </ul>
{% endfor %}
</li>
{% endfor %}

Categories

Resources