I m beginner.
I'm trying to access a related item of the model Product in the template layer of a ProductDetailView. How can I retrieve the ImageFields of the Products' Brand's BrandImages? I have to traverse one forward and one reverse ForeignKey.
Edited to include get_logo_url
What is wrong with the get_logo_url function?
products/models.py
class Product(models.Model):
brand = TreeForeignKey('Brand', verbose_name='parent category', related_name='products', default='')
title = models.CharField(max_length=120)
description = models.TextField(max_length=500, blank=True, null=True)
price = models.DecimalField(decimal_places=2, max_digits=20)
active = models.BooleanField(default=True)
category = TreeForeignKey('Category', verbose_name='parent category', related_name='products', default='')
slug = models.SlugField(default='')
objects = ProductManager()
class Meta:
unique_together = ('slug', 'category')
def get_absolute_url(self):
return reverse("product_detail", kwargs={"pk":self.pk})
def __unicode__(self):
return self.title
def get_image_url(self):
img = self.productimage_set.first()
if img:
return img.image.url
return img
brands/models.py
def image_upload_to(instance, filename):
title = instance.brand.title
slug = slugify(title)
file_extension = filename.split(".")[1]
new_filename = "%s.%s" % (instance.id, file_extension)
return "products/%s/%s" % (slug, new_filename)
class BrandImage(models.Model):
brand = models.ForeignKey('Brand', related_name='brandimages')
is_slider = models.BooleanField(default=False)
is_featured = models.BooleanField(default=False)
is_logo = models.BooleanField(default=False)
image = models.ImageField(upload_to=image_upload_to)
def __unicode__(self):
return self.brand.title
def get_logo_url(self):
if is_logo:
img = self.brandimage_set.first()
if img:
return img.image.url
return img
def thumb(self):
if self.image:
return u'<img src="%s" width=120 height=120 />' % (self.image.url)
else:
return u'No image file found'
thumb.allow_tags = True
class Brand(MPTTModel):
title = models.CharField(max_length=50, default='')
parent = TreeForeignKey('self', null=True, blank=True, verbose_name='parent brand', related_name='brands')
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return reverse('brands', kwargs={'path': self.get_path()})
def __unicode__(self):
return self.title
template
<div class="rightpart">
<div class="prodbrand h2">
<h1>{{ product.brand }}</h1>
<div class="brandlogo">
{% for image in product.brand.brandimages.all %}
<img src="{{image.get_logo_url }}"/>
{% endfor %}
</div>
</div>
<div class="prodtitle"><h2>{{ product.title }}</h2>
</div>
views.py
class ProductDetailView(DetailView):
model = Product
template_name = 'products/product.html'
def get_context_data(self , *args , **kwargs):
context = super(ProductDetailView , self).get_context_data(*args,**kwargs)
instance = self.get_object()
context["related"] = Product.objects.get_related(instance)
return context
urls.py
url(r'^$', ProductDetailView.as_view(), name='products'),
is there a way to access foreign fields in django templates like this?
As you are using a ListView to display your products there's several things to notice:
get_context_data() must return a dictionary: return context is missing
super().get_context_data should be called with *args,**kwargs incase you decide to subclass the ProductListView at a later point in time.
super().get_context_data will contain a object_list key which contains the list of objects returned by get_queryset(), in your case objects of class Product.
When accessing a property from a template, django will attempt to call it without parameters if it is callable. This is often useful e.g.: for {{ request.user.is_authenticated }} or product.brand.brandimages.all
Your template should look like this:
product_list.html
{% for product in object_list %}
<div class="rightpart">
<div class="prodbrand h2">
<h1>{{ product.brand }}</h1>
<div class="brandlogo">
{% for image in product.brand.brandimages.all %}
<img src="{{image.image.url}}"/>
{% endfor %}
</div><!-- End brandlogos -->
</div><!-- End prodbrand -->
<div class="prodtitle">
<h2>{{ product.title }}</h2>
</div>
</div><!-- End rightpart -->
{% endfor %}
Take into account that this will incur several database lookups from the template. You generally want to avoid the case that your presentation layer reaches into the database, which is why you should prefer to do the database lookup in the corresponding View. Also for property access consider using select_related and prefetch_related as appropriate to avoid unneeded database queries.
views.py
class ProductListView(ListView):
model = Product
queryset = Product.objects.all().active()
def get_context_data(self, *args, **kwargs):
context = super(ProductListView, self).get_context_data(*args, **kwargs)
context["now"] = timezone.now()
return context
def get_queryset(self, *args, **kwargs):
# We call super() for potential subclasses
queryset = super(ProductListView, self).get_context_data(*args, **kwargs)
queryset.prefetch_related('brand__brandimages')
return queryset
Related
I'm trying to build a simple Django gallery that allows photos to be added to an album. Everything thus far is working fine (upload photo, add to album, paginated all photos listing, photo details/display pages, etc), until I try to display the 'album' pages. I can get the page to render the the album and all of the associated photos, but if I try to paginate the album, things start to get weird.
Here are my models:
# models.py
class Albums(models.Model):
id = models.AutoField(primary_key=True, unique=True)
name = models.CharField(max_length=500)
slug = models.SlugField(max_length=500, unique=True)
description = models.TextField(blank=True, null=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.slug:
self.slug = slugify(self.name)
return super().save(*args, **kwargs)
class Photos(models.Model):
id = models.AutoField(primary_key=True, unique=True)
album = models.ForeignKey(
Albums, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Album"
)
photo = models.ImageField(upload_to="photos/")
slug = models.CharField(max_length=16, null=False, editable=False) # I know I'm not using a SlugField here; that's on purpose
title = models.CharField(max_length=500)
description = models.TextField(blank=True, null=True)
upload_date = models.DateTimeField(
default=timezone.now, verbose_name="Date uploaded"
)
def __str__(self):
return self.title
def save(self, *args, **kwargs):
[...] # stuff to get the image's EXIF data and use that to set the slug
self.slug = str(datetime.strftime(img_date, "%Y%m%d%H%M%S"))
[...] # plus a bunch of other stuff that happens on save, not important here
super(Photos, self).save()
def delete(self, *args, **kwargs):
[...] # a bunch of stuff that happens on delete, not important here
super(Photos, self).delete()
And the view that's making me miserable:
# views.py
class PhotoAlbum(DetailView):
context_object_name = "album"
model = Albums
slug_field = "slug"
template_name = "page.html"
def get_photos(self, **kwargs):
# get the integer id of the album
a = Albums.objects.filter(slug=self.kwargs["slug"])
a_id = a.values()[0]["id"]
# get the list go photos in that album
photos = Photos.objects.all().order_by("-capture_date").filter(album_id=a_id)
return photos
def get_context_data(self, **kwargs):
context = super(PhotoAlbum, self).get_context_data(**kwargs)
context["photos"] = self.get_photos()
return context
These work in a basic template...
# album.html
<h2>{{ album.name }}</h2>
<p>{{ album.description }}</p>
<ul>
{% for photo in photos %}
<li>{{ photo.title }}</li>
{% endfor %}
</ul>
...but if I try to paginate the photos by changing...
photos = Photos.objects.all().order_by("-capture_date").filter(album_id=a_id)
...to...
photos = Paginator(Photos.objects.all().order_by("-capture_date").filter(album_id=a_id), 10)
...I get nothing.
In searching around, almost all of the questions I've seen relating to pagination are for DRF, so have kind of hit a wall here. I had all sorts of trouble getting the photo-to-album relationship to display (the get_photos() function), so am guessing part of my problem lies with that. Any suggestions on how to get this working?
Paginator will give you a paginator object, not the objects you are paginating. To get the objects, you first need to specify the page, so for example:
paginator = Paginator(Photos.objects.all().order_by("-capture_date").filter(album_id=a_id), 10)
photos = paginator.page(1)
photos here will then contain Photos objects that are on page 1.
Based on Brian Destura's comment, I went back and started again using a ListView and after re-reading the Django docs and a bit of messing about, arrived at this solution for my view which, based on a little testing, works for me:
class AlbumDetail(ListView):
context_object_name = "photos"
template_name = "page.html"
paginate_by = 6
def get_queryset(self):
a = Albums.objects.filter(slug=self.kwargs["slug"])
a_id = a.values()[0]["id"]
self.albums = get_object_or_404(Albums, slug=self.kwargs["slug"])
return Photos.objects.filter(album_id=a_id).order_by("-capture_date")
def get_context_data(self, **kwargs):
context = super(AlbumDetail, self).get_context_data(**kwargs)
context["album"] = self.albums
return context
viwes.py
class PhotoAlbum(ListView):
context_object_name = "album"
model = Albums
slug_field = "slug"
template_name = "page.html"
paginate_by = 5
page.html
This is pagination logic
{%if is_paginated %}
{%if page_obj.has_previous %}
<a style="margin-left: 20px; padding: 10px 20px;" class=" btn btn-primary" href="?page={{page_obj.previous_page_number}}"> 🢀{{page_obj.num_pages}} previous</a>
{% endif %}
{%if page_obj.has_next %}
{% if page_obj.num_pages == 1 %}
<a style="margin-left: 930px; padding: 10px 28px;" class=" btn btn-primary" href="?page={{page_obj.next_page_number}}">next 🢂</a>
{% else %}
<a style="margin-left: 880px; padding: 10px 28px;" class=" btn btn-primary" href="?page={{page_obj.next_page_number}}">next 🢂</a>
{% endif %}
{% endif %}
{% endif %}
I have looked for an answer and all I can find are answers related to forms. I haven't seen anything related to a model object. I have a model called PanelType and I am trying to loop through every object to display relevant information pertaining to each panel type on a html template. I have created 3 PanelType objects through the admin page, so there is not "None". I believe I have this setup correctly but it's returning an error of returning None objects. I'd appreciate any help.
models.py
class PanelType(models.Model):
name = models.CharField('Name', max_length=150, default='')
slug = models.SlugField(unique=True)
description = models.TextField('Description', null=True, blank=True)
date_created = models.DateTimeField(
'Date Created', auto_now_add=True, null=True)
display = models.BooleanField(default=True)
image = models.ImageField('Panel Image', null=True, blank=True)
def get_absolute_url(self):
return reverse("model_detail", kwargs={"pk": self.pk})
def __str__(self):
return f'{self.name}'
def save(self, *args, **kwargs):
if not self.slug or self.slug != slugify(self.name):
self.slug = slugify(self.name)
return super().save(*args, **kwargs)
views.py
def services(request):
context = {'panels': PanelType.objects.all()}
render(request, 'app/services.html', context)
html
<div class="service-container">
{% for panel in panels %}
<div class="card">
<div class="serviceBx" data-text="{{panel.name}}">
<img src="{{panel.image.url}}" alt="">
</div>
<div class="service-content">
<div>
<h3>{{panel.name}}</h3>
<p>{{panel.description}}</p>
Get Quote
</div>
</div>
</div>
{% endfor %}
</div>
You need to return the result of the render(…) function:
def services(request):
context = {'panels': PanelType.objects.all()}
# ↓ return the HttpResponse
return render(request, 'app/services.html', context)
In my model I have a ManyToManyField to select related products. I'm wondering what would be the best way to bring these into my view and render them in my template.
models.py
class Product(models.Model):
title = models.CharField(max_length=80)
category = models.ManyToManyField(ProductCategory)
featured_image = models.ImageField(upload_to=image_dir)
about_this_product = models.TextField()
standard_features = models.TextField(null=True)
featured = models.BooleanField(default=False)
related_models = models.ManyToManyField("self", blank=True, null=True)
model_slug = AutoSlugField(null=True, default=None,
unique=True, populate_from='title')
class Meta:
verbose_name_plural = "Products"
def __str__(self):
return self.title
views.py
def model_detail_view(request, category_slug, model_slug):
product_model = get_object_or_404(Product, model_slug=model_slug)
context = {
"title": "Products | %s" % product_model.title,
"product": product_model,
}
return render(request=request, template_name='main/product_model_detail.html', context=context)
You can use .prefetch_related(..) just like you do on any one-to-many relation in the view:
def my_view(request):
products = Product.objects.prefetch_related('related_models')
return render(request, 'some_template.html', {'products': products})
Then in the template, you can iterate over the .related_models collection:
{% for product in products %}
{{ product.title }}
related:
{% for rel in product.related_models.all %}
{{ rel.title }}
{% endfor %}
{% endfor %}
I'm trying to iterate through the images in my categories but I'm getting 'ImageFileDescriptor' object has no attribute 'objects' error
models.py
class Category(models.Model):
category_title = models.CharField(max_length=200)
category_image = models.ImageField(upload_to="category")
category_description = models.TextField()
slug = models.SlugField(max_length=200, unique=True, default=1)
def get_image_path(self, filename):
gallery_path = os.path.abspath(
os.path.join(settings.MEDIA_ROOT, self.slug))
if not os.path.isdir(gallery_path):
os.mkdir(gallery_path)
return os.path.join(gallery_path, filename)
gallery = models.ImageField(upload_to=get_image_path)
def create_directory(self):
gallery_path = os.path.abspath(
os.path.join(settings.MEDIA_ROOT, self.slug))
if not os.path.isdir(gallery_path):
os.mkdir(gallery_path)
def save(self, *args, **kwargs):
if not self.pk:
self.create_directory()
super().save(*args, **kwargs)
def delete(self, *args, **kwargs):
shutil.rmtree(os.path.join(settings.MEDIA_ROOT, self.slug))
# os.rmdir(os.path.join(settings.MEDIA_ROOT, self.slug))
super().delete(*args, **kwargs)
class Meta:
verbose_name_plural = "Categories"
def __str__(self):
return self.category_title
views.py
def category_detail_view(request, slug):
category = get_object_or_404(Category, slug=slug)
context = {
"gallery": Category.gallery.objects.all(),
}
return render(request, 'main/category_detail.html', context)
category_detail.html
{% for image in gallery %}
<div class="col-md-4">
<a href="{{ image.url }}"> <img src="{{ image.url }}" class="img-responsive img-thumbnail" width="304" height="236"/>
</a>
</div>
{% endfor %}
For some reason creating galleries in Django has been difficult for me. It seems as if there is no standard way that most people are doing this. Even if I get this to work, I don't see how one could manage the uploaded images from the admin panel. ex upload delete arrange etc
If you could shed some light on this for me that would be awesome
I have two models with a foreign key, one to many relationship so that I can get a repeater model (images) in the admin. The image repeater works fine, my problem is that the images for the field - video_stills saved on one of the film post/objects repeats across all film posts.
Here is my code:
model.py
from __future__ import unicode_literals
from django.db import models
from embed_video.fields import EmbedVideoField
from sorl.thumbnail import ImageField
class Timestamp(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
# Film Model
class Film(Timestamp):
title = models.CharField(max_length=255)
order = models.PositiveIntegerField(default=0, blank=False, null=False)
meta_description = models.TextField('Meta Description', max_length=170,
help_text='Content for description meta tag - Max 170 Characters')
slug = models.SlugField(unique=True)
image = models.ImageField(upload_to='thumb')
video = EmbedVideoField(blank=True)
director = models.CharField(max_length=255,blank=True)
cinematographer = models.CharField(max_length=255,blank=True)
producer = models.CharField(max_length=255,blank=True)
publish = models.BooleanField(default=False)
date_published = models.DateTimeField()
# thumb for admin
def image_thumb(self):
return '<img src="%s" height="200" width="300"/>' % (self.image.url)
image_thumb.short_description = 'Image'
image_thumb.allow_tags = True
# override the admin name + add ordering
class Meta(object):
ordering = ('order',)
verbose_name_plural = "Film Projects"
def __unicode__(self):
return self.title
# helper method
def get_absolute_url(self):
return "/film/%s/" % self.slug
def save(self, *args, **kwargs):
super(Film, self).save(*args, **kwargs)
# Film Stills Image Model
class FilmStillsImage(models.Model):
video_stills = models.FileField(upload_to = 'images')
film = models.ForeignKey(Film)
class Meta(object):
verbose_name_plural = "Film Stills"
views.py
# film single
def film_detail(request, slug):
film = get_object_or_404(Film, slug=slug)
# get all repeater images
film_grabs = FilmStillsImage.objects.all()
try:
next_film = film.get_next_by_date_published()
except Film.DoesNotExist:
next_film = None
try:
previous_film = film.get_previous_by_date_published()
except Film.DoesNotExist:
previous_film = None
return render(request, 'film/film_detail.html', {
'film': film,
'film_grabs': film_grabs,
'next_film': next_film,
'previous_film': previous_film
})
film_detail.html
<div class="section project-stills">
{% if film_grabs %}
<div class="row">
{% for filmstillsimage in film_grabs %}
<div class="grid-item four">
{% thumbnail filmstillsimage.video_stills "600x338" crop="center" as im %}
<img src="{{ im.url }}">
{% endthumbnail %}
</div>
{% if forloop.counter|divisibleby:4 %}
</div>
<div class="row">
{% endif %}
{% endfor %}
</div>
{% else %}
<p>Nothing found.</p>
{% endif %}
</div>
admin.py
class FilmStillsImageAdmin(admin.ModelAdmin):
pass
class FilmStillsImageInline(admin.StackedInline):
model = FilmStillsImage
max_num = 8
extra = 0
class FilmAdmin(SortableAdminMixin, admin.ModelAdmin):
model = Film
list_display = ('title', 'meta_description', 'image_thumb', 'video', 'director', 'cinematographer', 'producer', )
inlines = [FilmStillsImageInline]
prepopulated_fields = {'slug': ('title',)}
I'm sure its a simple fix, I have a feeling its because of objects.all() in my views, populating it to each item.
Apologies if its a stupid question and thanks for any help!!
You're passing all film grabs to the detail template, so naturally they'll all display. Instead of doing that, you should just follow the reverse relationship in the template to show the ones that are actually related:
FilmStillsImage
<div class="section project-stills">
<div class="row">
{% for filmstillsimage in film.filmstillsimage_set.all %}
....
and there is no need to pass film_grabs from the view at all.