I have 2 models: Post and Comment. Like this:
class Post(models.Model):
content = models.TextField()
class Comment(models.Model)
content = models.TextField()
owner = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
post_parent = models.ForeignKey(
Post,
on_delete=models.CASCADE,
null=False,
db_column='post_parent',
related_name='comments',
)
comment_ref = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
related_name='children_comment'
)
Suppose that comments can have sub-comments in 1 level. With a post's ID, I want to get 5 newest comments (not sub-comment), each of them is prefetched with their 3 newest sub-comments.
My current implementation after searching is:
sub_comments = Comment.objects.filter(
comment_ref=OuterRef('pk'),
post_parent=post_id
).order_by('-timestamp').values_list('id', flat=True)[:3]
queryset = Comment.objects.filter(
comment_ref=None,post_parent=post_id
).select_related('owner').prefetch_related(
Prefetch('children_comment', queryset=Comment.objects.filter(
id__in=Subquery(sub_comments)
), to_attr="cmt"))
That does not work. It doesn't limit the number of prefetched sub-comments. Please help me :D
You will need a date field on the Comment table so you can show the latest comments and sub comments.
I would introduce another model here so its easier for queries and writing tests personally for me.
Models.py
class Post(models.Model):
content = models.TextField()
class Comment(models.Model)
content = models.TextField()
owner = models.ForeignKey(User, null=False, on_delete=models.CASCADE)
post_parent = models.ForeignKey(
Post,
on_delete=models.CASCADE,
null=False,
db_column='post_parent',
related_name='comments',
)
date_posted = models.DateTimeField()
class ChildComment(models.Model):
parent_comment = models.ForeignKey(
Comment,
on_delete=models.CASCADE,
null=True,
related_name="child_comments"
)
Views.py
post = Post.objects.get(id=<some_id>)
comments = Comment.objects.filter(
post_parent_id=post.id
).prefetch_related(
'child_comments'
).order_by(
'-date_posted'
)[:5]
recent_comments = {}
for comment in comments:
recent_comments[comment] = [
comment.child_comments.all().order_by(
'-parent_comment__date_posted'
)[:3]
]
Pass recent_comments to the template context and then in a template file use it like:
{% for recent_comment, child_comments in recent_comments.items %}
{{ recent_comment.content }}
{% for child_comment in child_comments %}
{{ child_comment.content }}
{% endfor %}
{% endfor %}
You will need a DateTimeField to get the latest comments and child comments in an order_by, but this query should work. This may not be ideal if you were returning thousands of comments, but only 5 at a time, this will be fast.
comments = Comment.objects.filter(
post_parent_id=post.id
)[:5]
root_comment_ids = [comment.id for comment in comments]
child_comments = Comment.objects.filter(
comment_ref_id__in=root_comment_ids
)[:3]
Related
I have three models Order, OrderEntries and Product
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
order_entries = models.ManyToManyField(OrderEntries, blank=True)
...
class OrderEntries(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.IntegerField()
class Product(models.Model):
title = models.CharField(max_length=1000)
slug = models.SlugField(blank=True, null=True, unique=True)
Now, I have to get history of purchased products. As I can get all previous orders placed by a user like this
queryset = Order.objects.filter(user=self.request.user, client=self.request.client)
But how I can get list of products from all those orders?
If you specify a related_name on the ForeignKey field as follows:
product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='products')
You could then access the products in your template, for example:
{% for order in orders %}
{% for order_entry in order.order_entries %}
{% for product in order_entry.products.all %}
{{ product }}
Please note: it isn't necessary to set related_name. If you don't set it, you can use BLA_set where BLA is the name of the field e.g.:
{% for product in order_entry.product_set.all %}
maybe this help :
product=[]
for e in queryset:
for ord in e['order_entries']:
for pr in ord['product'] :
product.append(pr)
I have three tables in my model:
class Recipe(models.Model):
title = models.CharField(max_length=200)
excerpt = models.TextField(null=True)
category = models.CharField(max_length=10, default='FoodGroup')
cookTime = models.CharField(max_length=20, default='15Min')
prepTime = models.CharField(max_length=20, default='5Min')
process = models.TextField(null=True)
slug = models.SlugField(max_length=100, unique=True)
updated = models.DateTimeField(auto_now=True)
published = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('recipe:single', args=[self.slug])
class Meta:
ordering = ['-published']
def __str__(self):
return self.title
class Ingredient(models.Model):
IngName = models.CharField(max_length=30)
IngType = models.CharField(max_length=20, null=True)
def __str__(self):
return self.IngName
class RecipeIngredients(models.Model):
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE)
recipe = models.ForeignKey(Core, on_delete=models.CASCADE)
And the view looks like this:
class RecipeView(ListView):
model = RecipeIngredients
template_name = 'core/recipes.html'
context_object_name = 'recipe'
I'm trying to then view this data using a for loop:
{% for recipe in recipe %}
{{ recipe.title }}
{% endfor%}
but nothing gets pulled using this method, I've tried looking at the docs but I'm unsure I'm doing this in the best way? I had a look at the many-to-many section of the Docs but I thought a link-table may be more appropriate for my scenario.
edit for anyone else having similar issues:
Using Django's ManyToMany model function ended up being more appropriate:
https://docs.djangoproject.com/en/3.0/topics/db/examples/many_to_many/
Answer was correct in saying object_name was poorly chosen.
The name of your iterable must be different from context_object_name.
Try different name like:
{% for diff_name in recipe %}
{{ diff_name.title }}
{% endfor%}
I recently found that too much SQL query optimization issue. django-debug-tool reported hundreds of similar and duplicate queries. So, I'm trying to figure out the best efficiency of Django ORM to avoid unnecessary Queryset evaluation.
As you see the below Store model, a Store model has many Foreign key and ManyToManyFields. Due to that structure, there are many code snippets doing the blow on HTML template files such as store.image_set.all or store.top_keywords.all. Everything starts with store. In each store detail page, I simply pass a cached store object with prefetch_related or select_related. Is this a bad approach? Should I cache and prefetch_related or select_related each Foreign key or ManyToManyField separately on views.py?
HTML templates
{% for img in store.image_set.all %}
{{ img }}
{% endfor %}
{% for top_keyword in store.top_keywords.all %}
{{ top_keyword }}
{% endfor %}
{% for sub_keyword in store.sub_keywords.all %}
{{ sub_keyword }}
{% endfor %}
views.py
class StoreDetailView(View):
def get(self, request, *args, **kwargs):
cache_name_store = 'store-{0}'.format(store_domainKey)
store = cache.get(cache_name_store, None)
if not store:
# query = get_object_or_404(Store, domainKey=store_domainKey)
query = Store.objects.all().prefetch_related('image_set').get(domainKey=store_domainKey)
cache.set(cache_name_store, query)
store = cache.get(cache_name_store)
context = {
'store': store,
}
return render(request, template, context)
models.py
class Store(TimeStampedModel):
categories = models.ManyToManyField(Category, blank=True)
price_range = models.ManyToManyField(Price, blank=True)
businessName = models.CharField(unique=True, max_length=40,
verbose_name='Business Name')
origin = models.ForeignKey(Origin, null=True, on_delete=models.CASCADE, blank=True)
ship_to = models.ManyToManyField(ShipTo, blank=True)
top_keywords = models.ManyToManyField(Keyword, blank=True, related_name='store_top_keywords')
sub_keywords = models.ManyToManyField(SubKeyword, blank=True, related_name='store_sub_keywords')
sponsored_stores = models.ManyToManyField(
'self', through='Sponsorship', symmetrical=False, related_name='sponsored_store_of_store')
similar_stores = models.ManyToManyField(
'self', through='Similarity', symmetrical=False, related_name='similar_store_of_store')
shortDesc = models.TextField(blank=True, verbose_name='Short Description')
longDesc = models.TextField(blank=True, verbose_name='Long Description')
returnPol = models.TextField(verbose_name='Return Policy', blank=True)
returnUrl = models.CharField(max_length=255, null=True, blank=True, verbose_name='Return Policy URL')
likes = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, editable=False)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False, on_delete=models.CASCADE,
related_name='stores_of_created_by', null=True, blank=True)
updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, editable=False, on_delete=models.CASCADE,
related_name='stores_of_updated_by', null=True, blank=True)
I really wouldn't advise custom caching/performance optimisation, unless it's a very last resort. Django has great docs on querysets and optimisation - if you follow those, it should be rare for you to experience major performance issues that require custom workarounds.
I think the issue here is that you're printing your objects in a template and hence calling their str() method. There's nothing wrong with this, but I'd check what variables you're using in your str() methods. I suspect you're referencing other models? I.e. the str() method in your image model (or whatever) is doing something like image.field.other_field. In this case, your query should look like:
queryset = Store.objects.prefetch_related('image_set__field')
Your final queryset may look like:
queryset = Store.objects.prefetch_related('image_set__field1', 'image_set__field2', 'top_keywords__field3', ...)
Note that you can still pass this into get_object_or_404 like so:
get_object_or_404(queryset, pk=<your_stores_id>)
Hope this helps.
Hi Guys I am trying to figure this out but not having any luck.
So I am showing my events in the homepage which shows how many seats are available, once the user has made a booking I would like to minus that from the amount showing on the homepage.
But I am already stuck at adding all the values up for that event in the booking model to minus from that amount.
So this is what I have
model for events
class Events(models.Model):
ACTIVE = (('d', "Deactivated"), ('e', "Expired"), ('a', "Active"), ('b', "Drafts"),)
ALCOHOL = (('0','No bring own alcohol'),('1','There will be complimentary wine pairing'))
user = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=50, blank=True, default='')
date = models.DateField()
time = models.TimeField()
price = models.CharField(max_length=240, blank=True, default='')
seats = models.IntegerField()
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
model for bookings
class Bookings(models.Model):
OPTIONS_STATUS = (('y', "Yes"), ('n', "No"), ('p', "Pending"),)
user = models.ForeignKey(User, on_delete=models.CASCADE)
event = models.ForeignKey(Events, on_delete=models.CASCADE)
eventdate = models.DateField()
event_amount = models.CharField(max_length=50, blank=True, default='')
guests = models.IntegerField()
bookingstatus = models.CharField(max_length=50, default='p', blank=True, choices=OPTIONS_STATUS)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
my homepage how I get my data into a loop form the view
today = datetime.now().strftime('%Y-%m-%d')
events_list_data = Events.objects.filter(active='a').filter(Q(date__gte=today)|Q(date=today)).order_by('date')
How I am trying to show this in my template
{% for event_list in events_list_data %}
SHOW WHAT EVER DATA I AM SHOWING NOT NEEDED FOR HELP ON
{% for bookingguests in
event_list.bookings_set.all %}
{{ bookingguests.guests }}
{% endfor %}
Seats Left
{% endif %}
Generally, purpose of templates is not to implement logic. All the logic should go into your views. I would recommend you to do that in your views and either store it in a dict or a list and send it to front-end.
Once the user made a booking, if you want to modify the value on the HTML without reloading, you may need to use jQuery/javascript. Otherwise, if you are fine with reloading the page by rendering it again with calculations from the backend.
By using jQuery:
$("#balance-id").html(logic to get the balance)
By Calculating in views:
from django.db.models import Sum
user_balance = user.balance - events_data.aggregate(Sum('price'))
return render('path/to/template', {'events_list': events_list_object, 'user_balance':user_balance})
In the template:
{{user_balance}} seats left
Let me know in case of any questions.
Note: If you want to write some logic into your templates, use template tags. It can help you with whatever it can with its limited functionality.
I'm working on a comic book database and there are main covers and variant covers. I have a page that shows all the Main covers, but I'd like to combine the variant covers too, in order of the publication date. This is what part of my models look like:
class Image(models.Model):
CATEGORY_CHOICES = (
('Cover', 'Cover'),
('Scan', 'Scan'),
('Other', 'Other'),
)
title = models.CharField(max_length=128)
number = models.CharField(max_length=20, help_text="Do not include the '#'.")
image = models.ImageField(upload_to="images/")
category = models.CharField(max_length=10, choices=CATEGORY_CHOICES)
### The variant cover is determined by the category_choice 'Cover'. ###
contributor = models.ManyToManyField(Contributor, blank=True, null=True)
date_added = models.DateField(auto_now_add=True, auto_now=True)
def __unicode__(self):
return self.title
class Meta:
ordering = ['title']
class Issue(models.Model):
CATEGORY_CHOICES = (
('Major', 'Major'),
('Minor', 'Minor'),
('Cameo', 'Cameo'),
('Other', 'Other'),
)
title = models.ForeignKey(Title)
number = models.CharField(max_length=20, help_text="Do not include the '#'.")
pub_date = models.DateField(blank=True, null=True)
cover_image = models.ImageField(upload_to="covers/", blank=True, null=True)
### This would be where the main image goes. ^^^ ###
images = models.ManyToManyField(Image, related_name="images_inc", blank=True, null=True)
### This is where the variant covers go.^^^ ###
has_emma = models.BooleanField(help_text="Check if Emma appears on the cover.")
My views.py for the main cover page looks like this:
def covers(request):
sort_by = request.GET.get('sort', 'pub_date')
if sort_by not in ['-date_added', 'date_added', '-pub_date', 'pub_date']:
sort_by = '-date_added'
issues = Issue.objects.filter(has_emma=True).order_by(sort_by).select_related(depth=1)
return render_to_response('comics/covers.html', {'issues': issues}, context_instance=RequestContext(request))
But I would like to display the variant covers too and not just the cover_image. Is there a way to do this? Maybe with something image and then filtering the category (of the Image model by cover)?
I, of course, can do this:
def variants(request):
Issue.objects.filter(has_emma=True).order_by(sort_by).select_related(depth=1)
images = Image.objects.filter(category='Cover').order_by('id')
return render_to_response('comics/variants.html', {'images': images}, context_instance=RequestContext(request))
But that does not give me enough flexibility as def covers does, and I want them combined and sorted by pub_date, like def covers.
Edit
models.py:
class Image(models.Model):
CATEGORY_CHOICES = (
('Cover', 'Cover'),
('Scan', 'Scan'),
('Other', 'Other'),
)
title = models.CharField(max_length=128)
image = models.ImageField(upload_to="images/")
category = models.CharField(max_length=10, choices=CATEGORY_CHOICES)
date_added = models.DateField(auto_now_add=True, auto_now=True)
def __unicode__(self):
return self.title
class Meta:
ordering = ['title']
class Issue(models.Model):
title = models.ForeignKey(Title)
number = models.CharField(max_length=20)
######
has_emma = models.BooleanField(help_text="Check if cover appearance.")
cover_image = models.ImageField(upload_to="covers/", blank=True, null=True)
images = models.ManyToManyField(Image, related_name="images_inc", blank=True, null=True)
######
def get_images(self):
''' Returns a list of all cover images combined,
"main" cover image first.
'''
images = [self.cover_image]
for image in self.images.filter(category='Cover'):
images.append(image.image)
return images
views.py:
def covers(request):
sort_by = request.GET.get('sort', '-pub_date')
if sort_by not in ['-date_added', 'date_added', '-pub_date', 'pub_date']:
sort_by = '-date_added'
issues = Issue.objects.filter(has_emma=True).order_by(sort_by)
return render_to_response('template.html', {'issues': issues,}, context_instance=RequestContext(request))
template.html:
{% for issue in issues %}{% for image in issue.get_images %}{{ image.image }}{% endfor %}{% endfor %} - displays nothing, however, {% for issue in issues %} {% for image in issue.get_images %} {{ issue.cover_image }} {% endfor %} {% endfor %} will repeatedly display the cover_image of the Issue model if there are variant covers, which are categorized in the Image model.
What can I do to fix this, so that it shows everything correctly? And for the record again, I want it to display the {{ cover_image }} (from the Issue model) and the {{ image.image }} as defined by the Image model combined.
If I understand your problem correctly, one way to solve it would be adding a method to Issue class like this:
class Issue(models.Model):
# fields...
def get_images(self):
''' Returns a list of all cover images combined,
"main" cover image first.
'''
images = [self.cover_image]
for image in self.images.filter(category='Cover'):
images.append(image.image)
return images
Then, in your template, you can do, for example, {% for image in issue.get_images %}....
(If it's not exactly what you need—then, I think, it would be better if you provide some template code as an example of what you're trying to achieve.)