I find myself repeatedly cycling through the same set of data, accessing database several time for the same loops to achieve displaying the correct data on one template, here's the code:
<!-- item images and thumbnails -->
<div class="row">
<div class="col-12 col-sm-8">
<div id="item{{item.pk}}Carousel" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
{% for image in item.itemimage_set.all %}
<li data-target="#item{{item.pk}}Carousel" data-slide-to="{{forloop.counter0}}"
{% if forloop.first %} class="active" {% endif %}></li>
{% endfor %}
</ol>
<div class="carousel-inner shadow-lg rounded-sm">
{% for image in item.itemimage_set.all %}
<div class="carousel-item {% if forloop.first %} active {% endif %}">
<img src="{{image.image.url}}" class="d-block w-100" alt="...">
</div>
{% endfor %}
</div>
{% if item.itemimage_set.count > 1 %}
<a class="carousel-control-prev" href="#item{{item.pk}}Carousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#item{{item.pk}}Carousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
{% endif %}
</div>
</div>
<div class="pl-sm-0 col-12 col-sm-4 d-flex flex-wrap align-content-start">
{% for image in item.itemimage_set.all %}
<div class="col-4
{% if item.itemimage_set.count > 3 %}
col-sm-6
{% else %}
col-sm-8
{% endif %}
mt-2 px-1 mt-sm-0 pb-sm-2 pt-sm-0 mb-0">
<img src="{{image.image.url}}" alt="" class="col-12 p-0 rounded-sm shadow-sm"
data-target="#item{{item.pk}}Carousel" data-slide-to="{{forloop.counter0}}">
</div>
{% endfor %}
</div>
</div>
<!-- /item images and thumbnails -->
The above code renders the item's itemimage bootstrap carousel and on the same page, an extra carousel modal:
<!-- itemImageModal -->
<div class="modal fade" id="itemImageModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalCenterTitle"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered col-12 col-md-8 modal-lg" role="document">
<div class="modal-content">
<div class="col-12 px-0">
<div id="itemImage{{item.pk}}Carousel" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
{% for image in item.itemimage_set.all %}
<li data-target="#itemImage{{item.pk}}Carousel" data-slide-to="{{forloop.counter0}}"
{% if forloop.first %} class="active" {% endif %}></li>
{% endfor %}
</ol>
<div class="carousel-inner shadow-lg rounded-sm">
{% for image in item.itemimage_set.all %}
<div class="carousel-item {% if forloop.first %} active {% endif %}">
<img src="{{image.image.url}}" class="d-block w-100" alt="...">
</div>
{% endfor %}
</div>
{% if item.itemimage_set.count > 1 %}
<a class="carousel-control-prev" href="#itemImage{{item.pk}}Carousel" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#itemImage{{item.pk}}Carousel" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
Data structure:
class Item(models.Model):
name = ... etc.
class ItemImage(models.Model):
item = models.ForeignKey(Item, on_delete=models.CASCADE)
image = models.ImageField(upload_to='itemimages', null=True, blank=True)
As you can see, there are 5 forloop cycles in the template which leads to frequent queries from the database over the same set of data.
What I've tried:
I've tried to replace {% for image in item.itemimage_set.all %} with {% for image in item.load_related_itemimage %}, and in the models.py:
class Item(models.Model):
name = ...
def load_related_itemimage(self):
return self.itemimage_set.prefetch_related('image')
and this prompt error:
'image' does not resolve to an item that supports prefetching - this is an invalid parameter to prefetch_related().
I'm actually quite new to django and am not sure how to use select_related or prefetch_related but so far I've employed them and managed to reduce database query from 150 to 30+. And I think the frequency can be further reduced due to the silly cycles above as you can see.
forward the Debug Toolbar data for the page:
SELECT "appname_itemimage"."id",
"appname_itemimage"."item_id",
"appname_itemimage"."image"
FROM "appname_itemimage"
WHERE "appname_itemimage"."item_id" = '19'
5 similar queries. Duplicated 5 times.
5 similar queries. Duplicated 5 times. is bad right?
view.py
class ItemDetailView(DetailView):
'''display an individual item'''
model = Item
template_name = 'boutique/item.html'
Thanks for #Iain Shelvington's answer, his solution for the above code works like a charm, how about this:
I think they are related so I didn't separate this to raise another question -- what if the item itself is in a forloop? how to use prefetch_related in this situation? thanks!
{% for item in subcategory.item_set.all %}
<img src="{{ item.itemimage_set.first.image.url }}">
{% endfor %}
because I need to access item.itemimage_set when looping through subcateogry.item_set, and this is a much worse problem than before, because it raises 19 repetition in loading itemimage
This template is rendered by a ListView
class CategoryListView(ListView):
'''display a list of items'''
model = Category
# paginate_by = 1
template_name = 'boutique/show_category.html'
context_object_name = 'category_shown'
def get_queryset(self):
qs = super().get_queryset().get_categories_with_item()
self.gender = self.kwargs.get('gender') # reuse in context
gender = self.gender
request = self.request
# fetch filter-form data
self.category_selected = request.GET.get('category_selected')
self.brand_selected = request.GET.get('brand_selected')
self.min_price = request.GET.get('min_price')
self.max_price = request.GET.get('max_price')
if gender == 'women':
self.gender_number = 1
elif gender == 'men':
self.gender_number = 2
else:
raise Http404
get_category_selected = Category.objects.filter(
gender=self.gender_number, name__iexact=self.category_selected).first()
category_selected_pk = get_category_selected.pk if get_category_selected else None
get_subcategory_selected = SubCategory.objects.filter(
category__gender=self.gender_number, name__iexact=self.category_selected).first()
subcategory_selected_pk = get_subcategory_selected.pk if get_subcategory_selected else None
category_pk = category_selected_pk if category_selected_pk else self.kwargs.get(
'category_pk')
subcategory_pk = subcategory_selected_pk if subcategory_selected_pk else self.kwargs.get(
'subcategory_pk')
# print('\nself.kwargs:\n', gender, category_pk, subcategory_pk)
if gender and not category_pk and not subcategory_pk:
qs = qs.get_categories_by_gender(gender)
# print('\nCategoryLV_qs_gender= ', '\n', qs, '\n', gender, '\n')
return qs
elif gender and category_pk:
qs = qs.filter(pk=category_pk)
# print('\nCategoryLV_qs_category= ', '\n', qs, '\n')
return qs
elif gender and subcategory_pk:
qs = SubCategory.objects.annotate(Count('item')).exclude(
item__count=0).filter(pk=subcategory_pk)
self.context_object_name = 'subcategory_shown'
# print('\nCategoryLV_qs_sub_category= ', '\n', qs, '\n')
return qs
def get_validated_cats(self):
categories_validated = []
subcategories_validated = []
items_validated = []
brand_selected = self.brand_selected
min_price = self.min_price
if min_price == '' or min_price is None:
min_price = 0
max_price = self.max_price
if max_price == '' or max_price is None:
max_price = 999999
for item in Item.objects.select_related('category', 'subcategory', 'tag').filter(category__gender=self.gender_number):
if int(min_price) <= item.final_price < int(max_price):
if brand_selected is None or brand_selected == 'бренд' or item.brand.name == brand_selected:
items_validated.append(item)
if item.category not in categories_validated:
categories_validated.append(item.category)
if item.subcategory not in subcategories_validated:
subcategories_validated.append(item.subcategory)
return categories_validated, subcategories_validated, items_validated
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['brands'] = Brand.objects.all()
cat_valid, subcat_valid, items_valid = self.get_validated_cats()
context['filter_context'] = {
'gender': self.gender,
'gender_number': self.gender_number,
'category_selected': self.category_selected,
'brand_selected': self.brand_selected,
'min_price': self.min_price,
'max_price': self.max_price,
'categories_validated': cat_valid,
'subcategories_validated': subcat_valid,
'items_validated': items_valid,
}
# print(context)
return context
You should use prefetch_related to prefetch "itemimage_set" so that every time you access item.itemimage_set.all you get the cached result
item = get_object_or_404(Item.objects.prefetch_related('itemimage_set'), pk=pk)
For a DetailView
class ItemDetailView(DetailView):
model = Item
queryset = Item.objects.prefetch_related('itemimage_set')
Related
I'm applying pagination on my sports.html page. In views.py I'm using ListView and using paginate_by = 2 but issue is pagination is not taking effect on webpage.
Pagination numbers are visible on page and when clicking on those page numbers it's not showing any error but all posts are visible on all pages, posts are not divided by paginate_by value.
Can anyone point out what I'm doing wrong here ?
views.py
class SportsList(ListView):
model = Sports
template_name = 'frontend/sports.html'
paginate_by = 2
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['main'] = Main.objects.all()
context['sports'] = Sports.objects.all()
return context
sports.html
<section class="blog_area">
<div class="container">
<div class="row">
<div class="col-lg-8">
<div class="blog_left_sidebar">
{% for sport in sports %}
<article class="row blog_item">
<div class="col-md-3">
<div class="blog_info text-right">
<ul class="blog_meta list">
<li>XAR<i class="lnr lnr-user"></i></li>
<li>From: {{ sport.from_date }}<i class="lnr lnr-calendar-full"></i></li>
<li>To: {{ sport.to_date }}<i class="lnr lnr-calendar-full"></i></li>
<li>{{ sport.category }}<i class="lnr lnr-layers"></i></li>
</ul>
</div>
</div>
<div class="col-md-9">
<div class="blog_post">
<img src="{{ sport.featured_image.url }}" alt="">
<div class="blog_details">
<a href="{% url 'sports_details' sport.slug %}">
<h2>{{ sport.title }}</h2>
</a>
<p>{{ sport.content|truncatewords:30|safe }}</p>
View More
</div>
</div>
</div>
</article>
{% endfor %}
{% if is_paginated %}
<nav class="blog-pagination justify-content-center d-flex">
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a href="?page={{ page_obj.previous_page_number }}" class="page-link">
<span aria-hidden="true">
<span class="lnr lnr-chevron-left"></span>
</span>
</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active">{{ num }}</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item">{{ num }}</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a href="?page={{ page_obj.next_page_number }}" class="page-link" aria-label="Next">
<span aria-hidden="true">
<span class="lnr lnr-chevron-right"></span>
</span>
</a>
</li>
{% endif %}
</ul>
{% endif %}
</nav>
</div>
</div>
</div>
</div>
</section>
This is because you are creating your queryset on get_context_data. You are not respecting the listview structure. Try something like this on your view:
class SportsList(ListView):
queryset = Sport.objects.all()
context_object_name = “sports”
template_name = 'frontend/sports.html'
paginate_by = 2
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['main'] = Main.objects.all()
return context
You need to use the following in the views.py.
context_object_name = 'sports'
By default, While a Django view is executing, self.object_list will contain the list of objects (usually, but not necessarily a queryset) that the view is operating upon. You can see the reference in Django doc here.
generic list view
Of course, you are adding 'sports' in the context of def get_context_data but a Django generic list view need to get operated upon the value mentioned in context_object_name or the object_list.
Hence in your views.py use:
class SportsList(ListView):
model = Sports
template_name = 'frontend/sports.html'
paginate_by = 2
context_object_name = 'sports'
ordering = ["id"]
I'm trying to build a Wish list, I have a model for the listings, and a model for the wish list,
I have succeeded in making it work but..
I have to loop over all the listings first and match with the product id in my wish list so i can access the fields such as title and image..
is there any easier way to do this than looping over all the listings until finding the matched ones ?
views.py
def add_to_wishlist(request, product_id):
product = WishList.objects.filter(listing_id=product_id, user=request.user.username)
if product:
product.delete()
else:
product = WishList()
product.listing_id = product_id
product.user = request.user.username
product.save()
return HttpResponseRedirect(request.META['HTTP_REFERER'])
def wishlist(request):
product = WishList.objects.filter(user=request.user)
all_listings = AuctionListing.objects.all()
return render(request, "auctions/wishlist.html", {
'wishlist': product,
'all_listings': all_listings
})
models.py
class AuctionListing(models.Model):
title = models.CharField(max_length=100)
description = models.TextField()
start_bid = models.IntegerField(null=True)
image = models.CharField(max_length=1000, blank=True)
category = models.CharField(max_length=100)
seller = models.CharField(max_length=100, default="Default_Value")
class WishList(models.Model):
user = models.CharField(max_length=64)
listing_id = models.IntegerField()
wishlist.html
{% extends "auctions/layout.html" %}
{% block title %}Users Wishlist {% endblock %}
{% block body %}
<div class="col-12 mx-auto">
<h1 class="h3">My Wishlist</h1>
<div>Manage your wishlist</div>
{% if wishlist %}
{% for listing in all_listings %}
{% for product in wishlist %}
{% if listing.id == product.listing_id%}
<div class="card-mb-3">
<div class="row g-0">
<div class="col-md-4">
<img src="{{ listing.image }}" class="img-responsive" >
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">{{listing.title}}</h5>
<p class="card-text"> {{listing.description}}</p>
<p class="card-text"><small class="text-muted">{{listing.start_bid}}</small></p>
</div>
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}
{% else %}
<p class="card-text">No products have been added to your wishlist yet</p>
{% endif %}
</div>
{% endblock %}
Because you're getting all the WishList objects for the user, you don't need to just gather all AuctionListing objects and iterate over them. You can query them using the IDs on the WishLists you get back.
I'd do something like;
views.py
def wishlist(request):
wishlists = WishList.objects.filter(user=request.user)
listing_ids = wishlists.values_list('listing_id', flat=True)
listings = AuctionListing.objects.filter(pk__in=listing_ids)
return render(request, "auctions/wishlist.html", {
'wishlists': wishlists,
'listings': listings
})
wishlist.html
{% extends "auctions/layout.html" %}
{% block title %}Users Wishlist {% endblock %}
{% block body %}
<div class="col-12 mx-auto">
<h1 class="h3">My Wishlist</h1>
<div>Manage your wishlist</div>
{% if wishlists and listings %}
{% for listing in listings %}
<div class="card-mb-3">
<div class="row g-0">
<div class="col-md-4">
<img src="{{ listing.image }}" class="img-responsive" >
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">{{listing.title}}</h5>
<p class="card-text"> {{listing.description}}</p>
<p class="card-text"><small class="text-muted">{{listing.start_bid}}</small></p>
</div>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p class="card-text">No products have been added to your wishlist yet</p>
{% endif %}
</div>
{% endblock %}
I am trying to create a edit form to update the database using Django model Forms but the problem is that edit form part of the sizeProductMap.html page is not rendering when edit form (sizeProductMap_edit) request is made.
My models are as shown below.
models.py
class Product(models.Model):
prod_ID = models.AutoField("Product ID", primary_key=True)
prod_Name = models.CharField("Product Name", max_length=30, null=False)
prod_Desc = models.CharField("Product Description", max_length=2000, null=False)
prod_Price = models.IntegerField("Product Price/Piece", default=0.00)
prod_img = models.ImageField("Product Image", null=True)
def __str__(self):
return "{}-->{}".format(self.prod_ID,
self.prod_Name)
class Size(models.Model):
size_id = models.AutoField("Size ID", primary_key=True, auto_created=True)
prod_size = models.CharField("Product Size", max_length=20, null=False)
def __str__(self):
return "{size_id}-->{prod_size}".format(size_id=self.size_id,
prod_size=self.prod_size)
class SizeProductMapping(models.Model):
size_p_map_id = models.AutoField("Size & Product Map ID", primary_key=True, auto_created=True)
size_id = models.ForeignKey(Size, null=False, on_delete=models.CASCADE, verbose_name="Size ID")
prod_id = models.ForeignKey(Product, null=False, on_delete=models.CASCADE, verbose_name="Product Id")
def __str__(self):
return ".`. {}_____{}".format(self.size_id,
self.prod_id)
This is the form I used to add and edit the model.
forms.py
from django import forms
from user.models import SizeProductMapping
class SizeProductMapForm(forms.ModelForm):
class Meta:
model = SizeProductMapping
fields = ['size_id', 'prod_id']
Here is the view I created to add ,update and delete the record.
views.py
def sizeProductMap(request):
form = SizeProductMapForm(request.POST, request.FILES)
if request.method == 'POST':
if form.is_valid():
form.save()
return redirect("/admin1/sizeProductMap/")
else:
sizeProductMap_show = SizeProductMapping.objects.all()
# start paginator logic
paginator = Paginator(sizeProductMap_show, 3)
page = request.GET.get('page')
try:
sizeProductMap_show = paginator.page(page)
except PageNotAnInteger:
sizeProductMap_show = paginator.page(1)
except EmptyPage:
sizeProductMap_show = paginator.page(paginator.num_pages)
# end paginator logic
return render(request, 'admin1/sizeProductMap.html', {'sizeProductMap_show': sizeProductMap_show, 'form': form})
def sizeProductMap_delete(request, id):
sizeProductMap_delete = SizeProductMapping.objects.filter(size_p_map_id=id)
sizeProductMap_delete.delete()
return redirect('/admin1/productSizeMap')
def sizeProductMap_edit(request, id):
instance = SizeProductMapping.objects.get(size_p_map_id=id)
form = SizeProductMapForm(instance=instance)
if request.method == 'POST':
form = SizeProductMapForm(request.POST, instance=instance)
if form.is_valid():
form.save()
return redirect('/admin1/sizeProductMap')
return render(request, 'admin1/sizeProductMap.html', {'form': form})
This is my urls.
urls.py
from django.urls import path
from admin1 import views
urlpatterns = [
path('sizeProductMap/', views.sizeProductMap, name="admin-size-product-map"),
path('sizeProductMap_delete/<int:id>', views.sizeProductMap_delete, name="admin-size-product-map-delete"),
path('sizeProductMap_edit/<int:id>', views.sizeProductMap_edit, name="admin-size-product-map-edit"),
]
This is the Html page where I want to display the form according to the page request.
sizeProductMap.html
{% extends 'admin1/layout/master.html' %}
{% block title %}Size Product Map{% endblock %}
{% block main %}
<h1>
<center>Size Product Map</center>
</h1>
<div class="container">
<div class="row">
<div class="col-lg-2"></div>
<div class="col-lg-10">
{% if sizeProductMap_show %}
<button type="button" class="btn btn-primary mt-2" data-toggle="modal" data-target="#modal-primary">Add
Size Product Mapping
</button>
<div class="modal fade" id="modal-primary">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Add Size Product Mapping</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span></button>
</div>
<div class="modal-body mt-2">
<form action="{% url 'admin-size-product-map'%}" method="POST"
enctype="multipart/form-data">
{% csrf_token %}
<table border="1" class="table table-bordered border border-info">
<tr>
<th>
{{form.size_id.label_tag}}
</th>
<td>{{form.size_id}}</td>
</tr>
<tr>
<th>
{{form.prod_id.label_tag}}
</th>
<td>
{{form.prod_id}}
</td>
</tr>
</table>
<input type="Submit" name="Submit" value="Submit" class="btn btn-success w-50"><br>
<div class="modal-footer justify-content-between">
<button type="button" class="btn btn-outline-light" data-dismiss="modal">Close
</button>
</div>
</form>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div class="container-fluid ">
<div class="row">
<div class="card mt-2 border border-secondary">
<div class="card-header">
<h3 class="card-title ">Size Product Map Table</h3>
</div>
<!-- /.card-header -->
<div class="card-body">
<table class="table table-bordered border border-info">
<thead>
<tr>
<th>Size Product Mapping Id</th>
<th>Product ID</th>
<th>Size ID</th>
<th>Action</th>
</tr>
</thead>
<tbody class="justify-content-center">
{% for x in sizeProductMap_show %}
<tr>
<td>{{x.size_p_map_id}}</td>
<td>{{x.prod_id}}</td>
<td>{{x.size_id}}</td>
<td><a href="{% url 'admin-size-product-map-edit' x.size_p_map_id %}"
class="btn btn-outline-primary mt-2"><i
class="fa fa-pencil-square-o" aria-hidden="true"></i></a>
<a href="{% url 'admin-size-product-map-delete' x.size_p_map_id %}"
class="btn btn-outline-danger mt-2"><i
class="fa fa-trash" aria-hidden="true"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- /.card-body -->
<div class="card-footer clearfix ">
<ul class="pagination pagination-sm m-0 justify-content-center">
{% if sizeProductMap_show.has_previous %}
<li class="page-item"><a class="page-link"
href="?page={{sizeProductMap_show.previous_page_number}}">
Previous </a>
</li>
{% endif%}
{% for x in sizeProductMap_show.paginator.page_range %}
{% if sizeProductMap_show.number == x %}
<li class="page-item active"><a class="page-link" href="?page={{x}}">{{x}}</a></li>
{% else%}
<li class="page-item"><a class="page-link" href="?page={{x}}">{{x}}</a></li>
{% endif %}
{% endfor %}
{% if sizeProductMap_show.has_next %}
<li class="page-item"><a class="page-link"
href="?page={{sizeProductMap_show.next_page_number}}">
Next </a>
</li>
{% endif %}
</ul>
</div>
</div>
<!-- /.card -->
</div>
</div>
{% endif %}
{% if sizeProductMap_edit %}
<form action="{% url 'admin-size-product-map-edit' x.size_p_map_id %}" method="POST">
{% csrf_token %}
{{form.size_id}}
{{form.prod_id}}
</form>
{% endif %}
</div>
</div>
</div>
{% endblock %}
And if it is possible to reduce the number of line of code please also help. Thanks in advance.
I've found out the answer. There was a really a silly mistake by me.
In the sizeProductMap.html there is a mistake let me point out that:
sizeProductMap.html
{% if sizeProductMap_edit %}
<form action="{% url 'admin-size-product-map-edit' x.size_p_map_id %}" method="POST">
{% csrf_token %}
{{form.size_id}}
{{form.prod_id}}
</form>
{% endif %}
Here I am checking for instance {% if sizeProductMap_edit %} this is the wrong thing.
I have to check {% if instance %} according to my views.py.
I'm developing a app in django in which I need to show multiple post and each post has multiple images that I want to put in a carrousel, my problem is that only the first post show its images, the next ones can't show them inside the carrousel, but can be shown in other parts of the template.
Here's the model.py
class Product(models.Model):
"""Model definition for Rifa."""
user = models.ForeignKey('User', on_delete=models.CASCADE)
nombre = models.CharField('nombre', max_length=50)
fecha_inicio = models.DateTimeField('fecha inicio', auto_now=False, auto_now_add=False)
fecha_termino = models.DateTimeField('fecha termino', auto_now=False, auto_now_add=False)
descripcion = models.TextField()
precio = models.IntegerField()
class Meta:
verbose_name = 'product'
verbose_name_plural = 'products'
def get_absolute_url(self):
return reverse('product:product_detail', args=[str(self.id)])
def __str__(self):
return self.nombre
class PhotoProduct(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
photo = models.ImageField(upload_to='images/')
def __str__(self):
return self.product.nombre
Here's the views.py
class ProductDetailView(generic.DetailView):
model = Product
queryset = Product.objects.all()
template_name = 'product_detail.html'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['photo_list'] = PhotoProduct.objects.all()
return context
Here´s the template
<div id="carouselExampleIndicators" class="carousel slide" data-ride="carousel">
<ol class="carousel-indicators">
{% for photo in photo_list %}
{% if photo.product.pk == product.pk %}
<li data-target="#carouselExampleIndicators" data-slide-to="{{ forloop.counter0 }}"
class="{% if forloop.counter0 == 0 %} active {% endif %}"></li>
{% endif %}
{% endfor %}
</ol>
<div class="carousel-inner">
{% for photo in photo_list %}
{% if photo.product.pk == product.pk %}
<div class="carousel-item {% if forloop.counter0 == 0 %} active {% endif %}">
<img src="{{photo.photo.url}}" class="img-fluid d-block w-100" style="height: 300px;" alt="...">
</div>
{% endif %}
{% endfor %}
</div>
<a class="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleIndicators" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</div>
When I inspect the elements in the browser, the first product show 1, 2, 3... in the forloop counter, but the second product start at 4, I need to now how do I restart the counter in forloop
you could use boostarp owl carousel this way. this can get you some idea. you can check this doc as well https://owlcarousel2.github.io/OwlCarousel2/
<div class="col-lg-12 col-carousel ">
<div class="owl-carousel carousel-main">
{% for photo in photo_list %}
<img src="{{photo.photo.ur}}">
{% endfor %}
</div>
jquery for the carousel. you can reference this doc https://www.jqueryscript.net/demo/Powerful-Customizable-jQuery-Carousel-Slider-OWL-Carousel/
<script type="text/javascript">
$('.carousel-main').owlCarousel({
autoPlay: false,
center: true,
items: 3,
loop: true,
stopOnHover : true,
autoplayTimeout: 1500,
margin: 10,
navigation: true,
dots: false,
navigationText: ['<span class="fas fa-chevron-left fa-2x"></span>','<span class="fas fa-chevron-right fa-2x"></span>'],
});
I have a model that describes a Webpage. The source_upload field represents a screenshot of the webpage.
For adding site-objects to my application, I use a django class-based CreateView. This works really well.
Now I'm trying to add a semi-automatic way of adding sites. You can pass an URL to the view and the view fills the form automatically (and makes a screenshot of the webpage). The user should be able to review all the auto-extracted fields - especially the auto generated screenshot image - change them and hit the save button to add the object to the database and the image (if approved) to its final location.
I tried to implement this in the get_initial method of the view. This works quite well except for the screenshot-FileField. The path I set in initial['source_upload'] is not shown in the current: <link>part of the FileInput widget of the form.
How can I give the filefield an initial value?
models.py
class Site(models.Model):
def get_source_upload_path(instance, filename):
now = datetime.datetime.now()
return "appname/sites/{}/{}/{}/site_{}_{}".format(now.year, now.month, now.day, instance.pk, filename)
creationDate = models.DateTimeField(auto_now_add=True)
last_modifiedDate = models.DateTimeField(auto_now=True)
creator = models.ForeignKey('auth.User', related_name='siteCreated')
last_modifier = models.ForeignKey('auth.User', related_name='siteLast_modified')
date = models.DateTimeField(default=datetime.date.today)
title = models.CharField(max_length=240, blank=True)
body = models.TextField(max_length=3000)
source_url = models.URLField(blank=True)
source_upload = models.FileField(upload_to=get_source_upload_path, blank=True)
keywords = models.ManyToManyField("Keyword")
urls.py
url(r'site/add/$', views.SiteCreate.as_view(), name='site-add'),
url(r'site/add/(?P<source_url>[A-Za-z0-9\-._~:/\[\]#!$&\'\(\)\*\+,;=?#]+)/$', views.SiteCreate.as_view(), name='site-add-fromurl'),
forms.py
class SiteForm(ModelForm):
class Meta:
model = Site
fields = ['date', 'title', 'body', 'source_url', 'source_upload', 'keywords']
widgets = {
'keywords' : CheckboxSelectMultiple(),
}
views.py
class SiteCreate(LoginRequiredMixin, CreateView):
model = Site
template_name = 'appname/site_form.html'
form_class = SiteForm
success_url = reverse_lazy('appname:index')
def form_valid(self, form):
form.instance.creator = self.request.user
form.instance.last_modifier = self.request.user
return super(SiteCreate, self).form_valid(form)
def get_initial(self):
# Get the initial dictionary from the superclass method
initial = super(SiteCreate, self).get_initial()
try:
#get target url from request
fullpath = self.request.get_full_path()
fullpath = fullpath.split("/")
fullpath, querystring = fullpath[3:-1], fullpath[-1]
source_domain = fullpath[2]
fullpath = "/".join(fullpath)
fullpath += querystring
source_url = fullpath
if (not source_url.startswith("http://") and not source_url.startswith("https://")):
print("ERROR: url does not start with http:// or https://")
return initial
# ...
# extract title, date & others with BeautifulSoup
# ...
#extract screenshot (is there a better way?)
from selenium import webdriver
driver = webdriver.Firefox()
driver.get(source_url)
tmpfilename = "{}_{}.png".format(get_valid_filename(source_domain), get_valid_filename(title[:30]))
now = datetime.datetime.now()
tmpfilepath_rel = "appname/sites/tmp/{}/{}/{}/{}".format(now.year, now.month, now.day, tmpfilename)
tmpfilepath = settings.MEDIA_ROOT + tmpfilepath_rel
folder=os.path.dirname(tmpfilepath)
if not os.path.exists(folder):
os.makedirs(folder)
driver.save_screenshot(tmpfilepath)
driver.quit()
initial = initial.copy()
initial['source_url'] = source_url
initial['title'] = title
initial['date'] = soup_date
initial['body'] = body
initial['source_upload'] = tmpfilepath_rel
except KeyError as e:
print("no valid source_url found. zeige also ganz normales add/new template")
except IndexError as e:
print("no valid source_url found. zeige also ganz normales add/new template")
return initial
site_form.html (Used for Create and Update view)
{% extends "appname/base.html" %}
{% load staticfiles %}
{% block header %}
<link rel="stylesheet" type="text/css" href="{% static 'appname/model_forms.css' %}" />
{% endblock %}
{% block body %}
<form enctype="multipart/form-data" action="" method="post">{% csrf_token %}
<div class="fieldWrapper">
<div class="error">{{ form.date.errors }}</div>
<div class="label">{{ form.date.label_tag }}</div>
<div class="field">{{ form.date }}<br />{{ form.date.help_text }}</div>
<div class="floatclear"></div>
</div>
<div class="fieldWrapper">
<div class="error">{{ form.title.errors }}</div>
<div class="label">{{ form.title.label_tag }}</div>
<div class="field">{{ form.title }}<br />{{ form.title.help_text }}</div>
<div class="floatclear"></div>
</div>
<div class="fieldWrapper">
<div class="error">{{ form.body.errors }}</div>
<div class="label">{{ form.body.label_tag }}</div>
<div class="field">{{ form.body }}<br />{{ form.body.help_text }}</div>
<div class="floatclear"></div>
</div>
<div class="fieldWrapper">
<div class="error">{{ form.source_url.errors }}</div>
<div class="label">{{ form.source_url.label_tag }}</div>
<div class="field">{{ form.source_url }}<br />{{ form.source_url.help_text }}</div>
<div class="floatclear"></div>
</div>
<div class="fieldWrapper">
<div class="error">{{ form.source_upload.errors }}</div>
<div class="label">{{ form.source_upload.label_tag }}</div>
<div class="field">{{ form.source_upload }}<br />{{ form.source_upload.help_text }}</div>
<div class="floatclear"></div>
</div>
<div class="fieldWrapper">
<div class="error">{{ form.keywords.errors }}</div>
<div class="label">{{ form.keywords.label_tag }}</div>
<div class="field">
<ul class="checkbox-grid">
{% for kw in form.keywords %}
<li>
{{ kw.tag }}
<label for="{{ kw.id_for_label }}">
{{ kw.choice_label }}
</label>
</li>
{% endfor %}
</ul>
<div class="checkbox_help_text"><br />{{ form.keywords.help_text }}</div>
</div>
<div class="floatclear"></div>
</div>
<input type="submit" value="Save" />
</form>
<div id="ObjectHistory">
{% if site.pk %}
<p>Created by: {{ site.creator }}</p>
<p>Created on: {{ site.creationDate }}</p>
<p>Last modified by: {{ site.last_modifier }}</p>
<p>Last modified on: {{ site.last_modifiedDate }}</p>
<p>Now: {% now "Y-m-d H:i:s" %} <button>delete</button></p>
{% else %}
<p>This is a new Site!</p>
<p>Now: {% now "Y-m-d H:i:s" %}</p>
{% endif %}
</div>
{% endblock %}
This is because the value of FileField, as used by your form, isn't just the path to the file - it's an instance of FieldFile (see https://docs.djangoproject.com/en/1.10/ref/models/fields/#django.db.models.fields.files.FieldFile).
I'm not sure if you can instantiate a FieldFile directly, but at least you can do it by instantiating the model (you don't need to save it).
In views.py:
tmp_site = Site(source_upload=tmpfilepath_rel)
initial['source_upload'] = tmp_site.source_upload
Alternatively you can manually add a link to the file when rendering the html:
<div class="currently">{{ form.source_upload.value }}</div>