I have this in my template file:
{% get_latest_show as slideshow %}
{% for slide in slideshow.slide_set.all %}
<img src="{% thumbnail slide.image 1174x640 upscale %}" alt="{{slide.title}}" width="1174"/>
{% endfor %}
models.py
from django.db import models
import datetime
class Slide(models.Model):
title = models.CharField(max_length=50)
description = models.TextField(blank=True, null=True)
target_url = models.TextField(blank=True, null=True)
slideshow = models.ForeignKey('Slideshow')
image = models.ImageField(upload_to='slideshow', max_length=500, blank=True,null=True)
def __unicode__(self):
return self.title
class Slideshow(models.Model):
title = models.CharField(max_length=50)
pub_date = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=False)
class Meta:
ordering = ['-title']
def __unicode__(self):
return self.title
slide_tags.py
from django import template
from django.core.cache import cache
from django.contrib.contenttypes.models import ContentType
from slides.models import Slide, Slideshow
register = template.Library()
class GetSlideshowNode(template.Node):
"""
Retrieves the latest published slideshow
"""
def __init__(self, varname):
self.varname = varname
def render(self, context):
try:
show = Slideshow.objects.filter(published=True)[0]
except:
show = []
context[self.varname] = show
return ''
def get_latest_show(parser, token):
"""
Retrieves the latest published slideshow
{% get_latest_show as show %}
"""
args = token.split_contents()
argc = len(args)
try:
assert (argc == 3 and args[1] == 'as')
except AssertionError:
raise template.TemplateSyntaxError('get_latest_show syntax: {% get_latest_show as varname %}')
varname = None
t, a, varname = args
return GetSlideshowNode(varname=varname)
register.tag(get_latest_show)
The problem is that my slides are being displayed out of order. When I print slideshow.slide.set.all on the page, I see:
[<Slide: Slide 2>, <Slide: Slide 3>, <Slide: Slide 4>, <Slide: Slide 1>]
How do I get the slides to appear in order?
You want the slide_set to be ordered therefor the 'ordering' statement should be on the Slide model.
class Slide(models.Model):
# fields
class Meta:
ordering = ['-title']
This will cause Slide.objects.all() to return a queryset ordered by the title field. That is equivalent to slideshow.slide_set.all()
Related
I'm having problem with Django templates not receiving any data in image.url when having more than one object in context QuerySet all other data is working fine. If there is only one object in QuerySet, image.url works fine. I'm storing images on S3 and there is nothing wrong with the images or the bucket permission.
I'm new with Django templates is there anything I'm missing?
Here is the code:
models.py
`
from django.db import models
from django.core.validators import MaxValueValidator, MinValueValidator
from django.utils.translation import gettext_lazy as _
from site_content.models import PageCategory, Page
from site_content.utils import file_upload_to
class PpcOffersPagesManager(models.Manager):
def get_queryset(self):
return super(PpcOffersPagesManager, self) \
.get_queryset() \
.filter(page_category__category_name="ppc-offer-page")
class PpcOfferPages(Page):
objects = PpcOffersPagesManager()
class Meta:
proxy = True
def __str__(self):
return self.page_name
def save(self, *args, **kwargs):
self.page_category = PageCategory.objects.get(category_name="ppc-offer-page")
self.page_site_type = "ppc"
super(PpcOfferPages, self).save(*args, **kwargs)
class PpcOfferContent(models.Model):
related_page = models.ForeignKey(
Page, verbose_name=_("Page"),
related_name="ppc_offer_content",
on_delete=models.SET_NULL,
null=True
)
order = models.IntegerField(verbose_name=_("Order of offer appearance"), default=0)
image = models.ImageField(
verbose_name=_("Casino banner image"),
upload_to=file_upload_to,
null=True,
blank=True,
help_text="Casino banner image, Size (300x500)"
)
header = models.CharField(
verbose_name=_("Header text"),
max_length=500,
null=False,
blank=False
)
is_active = models.BooleanField(verbose_name=_("Is offer active"), default=True)
class Meta:
verbose_name = _('PPC_Offer Page Content')
verbose_name_plural = _('PPC_Offer Page Content')
def __str__(self):
return self.related_page.page_name
`
views.py
`
from django.views.generic import DetailView
from ppc_site_control.models import PpcOfferContent
class PpcOffersDetailView(DetailView):
model = PpcOfferContent
def get_template_names(self):
page = self.model.objects \
.filter(related_page__slug = self.kwargs['slug']) \
.first().related_page.page_template.template_path
return page
def get_object(self):
return self.model.objects \
.filter(related_page__slug=self.kwargs['slug']) \
.filter(is_active=True) \
.order_by('offer_order')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
`
.html
`
{% extends "slotsselection/base.html" %}
{% load i18n static reviews sstags %}
{% for offer in object %}
<div>
<div >
<img loading="lazy" src="{{offer.offer_image.url}}" alt="image">
</div>
<div>
<p>{{offer.header}}</p>
<p>{{offer.is_active }}</p>
</div>
<div>
{% endfor %}
`
I tried sending data in the context as new key and adding QuerySet object again with .values()(to exclude possibility of problem with "get_object" query) still have the same result. When I remove the images from template, all other data is displayed accordingly, without images.
I also tried with ListView same error
ValueError The 'image' attribute has no file associated with it.
Data in context:
`{'object': <QuerySet [<PpcOfferContent: PPC PageName>,
<PpcOfferContent: PPC PageName>, <PpcOfferContent: PPC PageName>,
<PpcOfferContent: PPC PageName>]>, 'view':
<ppc_site_control.views.PpcOffersDetailView object at 0x7f2d8230f430>}
`
The problem was caused from missing images because they were not required in the model the simplest solution is adding "if" condition in the template:
`{% if offer.offer_image %}
<img loading="lazy" src="{{offer.image.url}}" alt="Casino image">
{% endif %}`
I also manage to find workaround:
views.py
`from slotsselection.components.s3_settings import MEDIA_URL`
then in the context data I added
`def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['media_url'] = MEDIA_URL
return context
`
.html
and in html I removed .url to get the half of the link as string and added media_url
`<img loading="lazy" src="{{media_url}}{{offer.offer_image}}" alt="Casino image">`
not best approach but it works to.
I have a model for the Product Perfume, the product have different volume and prices.
I need to render the volume and price for each but i get "'function' object has no attribute 'prices'"
Any ideas? iam grateful for any suggestions
View:
from django.shortcuts import render
from django.views.generic import View, TemplateView
from products.models import Perfume, Pricing
def getIndex(request):
perfumes = Perfume.objects.all
thePrice= perfumes.prices.all()
return render(request, 'index.html', {'perfumes': perfumes, 'thePrice':thePrice})
Model
from django.conf import settings
from django.db import models
from django.utils import timezone
class Perfume(models.Model):
genderChoice = (
('unisex','unisex'), ('male', 'male'), ('female', 'female'))
name = models.CharField(max_length=50, default='')
brand = models.CharField(max_length=40, default='')
gender = models.CharField(max_length=7, choices=genderChoice, default='unisex')
description = models.TextField()
image = models.ImageField(upload_to='images/product_image')
created = models.DateField()
author =models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
active = models.BooleanField(default=False)
show_for_consumer = models.BooleanField(default=False)
def __str__(self):
return self.name
class Pricing(models.Model):
product =models.ForeignKey(Perfume, on_delete=models.CASCADE,related_name='prices')
price= models.DecimalField(max_digits=10, decimal_places=2)
volume= models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return 'Perfume {} - Price{} - Volume {}'.format(self.product.name,self.price, self.volume)
First, you need to correct the missing () in getting the perfumes queryset
perfumes = Perfume.objects.all()
This thePrice = perfumes.prices.all() is incorrect. You have to do this for every perfume, not for a queryset of perfumes.
Since Perfume and Pricing have a one-to-many relationship, you can directly access the prices from every perfume instance. This way you only need to pass the perfumes queryset
def getIndex(request):
perfumes = Perfume.objects.all()
return render(request, 'index.html', {'perfumes': perfumes})
Finally, in your template, you can call the prices like this
{% for product in perfumes %}
...
{% for p in product.prices.all %}
<option>{{p.volume}}ml - {{p.price}}$ </option>
...
{% endfor %}
{% endfor %}
I have managed to parse the list of advertisements, put some information in AdvertItem and load this item using AdvertLoader. But I could not figure out how I can get some extra information about each advertisement from item page details, put this additional information in the same AdvertItem object and then load the item with all information using AdvertLoader.
class AdvertLoader(ItemLoader):
default_input_processor = MapCompose(unicode.strip, remove_tags)
default_output_processor = Join()
class AdvertSpider(scrapy.Spider):
name = "adverts"
start_urls = [
"http://blablaadverts.com/",
]
adverts_list_xpath = '//table[#class="object-list-table"]/tbody/tr[#class="object-type-apartment"]'
advert_item_fields = {
'id': './#id',
'link': './/td[#class="object-name"]/h2[contains(#class, "object-title")]/a/#href',
'status': 'normalize-space(.//td[contains(#class, "object-media")]/div/p/a/span[contains(#class, '
'"sold-overlay-list")]/span/text())',
'state': './/td[#class="object-name"]/h2[contains(#class, "object-title")]/a/text()',
'city': './/td[#class="object-name"]/h2[contains(#class, "object-title")]/a/text()',
'zone': './/td[#class="object-name"]/h2[contains(#class, "object-title")]/a/text()',
'address': './/td[#class="object-name"]/h2[contains(#class, "object-title")]/a/text()',
'rooms': './/td[contains(#class, "object-rooms")]/text()',
'area': 'normalize-space(.//td[contains(#class, "object-m2")]/text())',
'price': 'normalize-space(.//td[contains(#class, "object-price")]/p/text())',
}
advert_details_xpath = '//table[contains(#class, "object-data-meta")]/tbody/tr'
advert_item_details_fields = {
'floor': './/td/text()',
'built_in_year': './/td/text()',
'condition': './/td/text()',
'ownership': './/td/text()',
'energy_level': './/td/text()',
}
contact_name = '//div[contains(#class, "object-article-contact")]/p[#class="fn"]/text()'
next_page = '//li[contains(#class, "next")]/a/#href'
def parse(self, response):
selector = Selector(response)
for advert in selector.xpath(self.adverts_list_xpath):
loader = AdvertLoader(AdvertItem(), selector=advert)
for field, xpath in self.advert_item_fields.iteritems():
loader.add_xpath(field, xpath)
# This request is not working as I expect.
yield scrapy.Request("http://blablaadverts.com/index.htmlnr=55&search_key=ca41231a29d2ab921aed02e864152c0e",
callback=self.parse_page2, meta={'loader': loader})
yield loader.load_item()
next_page = response.xpath(self.next_page).extract_first()
if next_page is not None:
next_page = response.urljoin(next_page)
yield Request(next_page, callback=self.parse)
def parse_page2(self, response):
selector = Selector(response)
loader = response.meta['loader'] # type: AdvertLoader
loader.selector = selector
loader.add_xpath('contact_name', self.contact_name)
# yield loader.load_item()
Below code saves only information about each advertisement without extra details from second item details page.
Function parse_page2() is working if I run it separately from parse() function.
How can I collect all information and only then load my AdvertItem object in loader?
I am not sure I get you correctly or not.
But change this part of code
# This request is not working as I expect.
yield scrapy.Request("http://blablaadverts.com/index.htmlnr=55&search_key=ca41231a29d2ab921aed02e864152c0e",
callback=self.parse_page2, meta={'loader': loader})
yield loader.load_item()
to
# This request is not working as I expect.
scrapy.Request("http://blablaadverts.com/index.htmlnr=55&search_key=ca41231a29d2ab921aed02e864152c0e",
callback=self.parse_page2, meta={'loader': loader})
loader.load_item()
And then yield in this function when all information is available.
def parse_page2(self, response):
selector = Selector(response)
loader = response.meta['loader'] # type: AdvertLoader
loader.selector = selector
loader.add_xpath('contact_name', self.contact_name)
yield loader.load_item()
What is the correct way to nest Item data?
For example, I want the output of a product:
{
'price': price,
'title': title,
'meta': {
'url': url,
'added_on': added_on
}
I have scrapy.Item of:
class ProductItem(scrapy.Item):
url = scrapy.Field(output_processor=TakeFirst())
price = scrapy.Field(output_processor=TakeFirst())
title = scrapy.Field(output_processor=TakeFirst())
url = scrapy.Field(output_processor=TakeFirst())
added_on = scrapy.Field(output_processor=TakeFirst())
Now, the way I do it is just to reformat the whole item in the pipeline according to new item template:
class FormatedItem(scrapy.Item):
title = scrapy.Field()
price = scrapy.Field()
meta = scrapy.Field()
and in pipeline:
def process_item(self, item, spider):
formated_item = FormatedItem()
formated_item['title'] = item['title']
formated_item['price'] = item['price']
formated_item['meta'] = {
'url': item['url'],
'added_on': item['added_on']
}
return formated_item
Is this correct way to approach this or is there a more straight-forward way to approach this without breaking the philosophy of the framework?
UPDATE from comments: Looks like nested loaders is the updated approach. Another comment suggests this approach will cause errors during serialization.
Best way to approach this is by creating a main and a meta item class/loader.
from scrapy.item import Item, Field
from scrapy.contrib.loader import ItemLoader
from scrapy.contrib.loader.processor import TakeFirst
class MetaItem(Item):
url = Field()
added_on = Field()
class MainItem(Item):
price = Field()
title = Field()
meta = Field(serializer=MetaItem)
class MainItemLoader(ItemLoader):
default_item_class = MainItem
default_output_processor = TakeFirst()
class MetaItemLoader(ItemLoader):
default_item_class = MetaItem
default_output_processor = TakeFirst()
Sample usage:
from scrapy.spider import Spider
from qwerty.items import MainItemLoader, MetaItemLoader
from scrapy.selector import Selector
class DmozSpider(Spider):
name = "dmoz"
allowed_domains = ["example.com"]
start_urls = ["http://example.com"]
def parse(self, response):
mainloader = MainItemLoader(selector=Selector(response))
mainloader.add_value('title', 'test')
mainloader.add_value('price', 'price')
mainloader.add_value('meta', self.get_meta(response))
return mainloader.load_item()
def get_meta(self, response):
metaloader = MetaItemLoader(selector=Selector(response))
metaloader.add_value('url', response.url)
metaloader.add_value('added_on', 'now')
return metaloader.load_item()
After that, you can easily expand your items in the future by creating more "sub-items."
I think it would be more straightforward to construct the dictionary in the spider. Here are two different ways of doing it, both achieving the same result. The only possible dealbreaker here is that the processors apply on the item['meta'] field, not on the item['meta']['added_on'] and item['meta']['url'] fields.
def parse(self, response):
item = MyItem()
item['meta'] = {'added_on': response.css("a::text").extract()[0]}
item['meta']['url'] = response.xpath("//a/#href").extract()[0]
return item
Is there a specific reason for which you want to construct it that way instead of unpacking the meta field ?
I having two fields for a Paragraph Model, with one of them being a ManyToMany field.
class Tag(models.Model):
tag = models.CharField(max_length=500)
def __unicode__(self):
return self.tag
admin.site.register(Tag)
class Paragraph(models.Model):
article = models.ForeignKey(Article)
text = models.TextField()
tags = models.ManyToManyField(Tag)
def __unicode__(self):
return "Headline: " + self.article.headline + " Tags: " + ', '.join([t.tag for t in self.tags.all()])
admin.site.register(Paragraph)
And my .txt files reflects the ManyToMany relationship to index tags-
{{object.text}}
{% for tag in object.tags.all %}
{{tag.tag}}
{% endfor %}
My views.py then uses SQS to search for all the tags (I want to accomplish this first before including text field) and retrieves those. So in this case, the query is "Politics"-
def politics(request):
paragraphs = []
sqs = SearchQuerySet().filter(tag="Politics")
paragraphs = [a.object for a in sqs[0:10]]
return render_to_response("search/home_politics.html",{"paragraphs":paragraphs},context_instance=RequestContext(request))
Edited:
and my search_indexes.py
class ParagraphIndex(indexes.SearchIndex, indexes.Indexable):
text= indexes.CharField(document=True, use_template=True)
tags= indexes.CharField(model_attr='tags')
def get_model(self):
return Paragraph
def index_queryset(self):
return self.get_model().objects
def load_all_queryset(self):
# Pull all objects related to the Paragraph in search results.
return Paragraph.objects.all().select_related()
However this doesn't retrive anything even though a few paragraphs have tags that are "Politics". Am I missing anything here or should I approach related data another way? I am a beginner with Haystack so any help will be much appreciated. Thanks in advance!
So this is a very useful article that helped me solve the problem.
Based on the article, this is how my search_indexes.py looks now:
class ParagraphIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
tags = indexes.MultiValueField()
def prepare_tags(self,object):
return [tag.tag for tag in object.tags.all()]
def get_model(self):
return Paragraph
def index_queryset(self):
return self.get_model().objects
def load_all_queryset(self):
# Pull all objects related to Paragraph in search results.
return Paragraph.objects.all().select_related()
and my views.py:
def politics(request):
paragraphs = []
sqs = SearchQuerySet().filter(tags='Politics')
paragraphs = [a.object for a in sqs[0:10]]
return render_to_response("search/home.html",
{"paragraphs":paragraphs},
context_instance=RequestContext(request))
And I am using elasticsearch for the engine. Hope this helps!