I'm trying to fix my Django-haystack combined with Elasticsearch search results to be exact.
The problem I have now is that when a user try for example, the "Mexico" query, the search results also returns deals in "Melbourne" which is far from being user-friendly.
Anyone can help me to fix this problem?
This is what I've tried so far but no good results:
My forms.py
from haystack.forms import FacetedSearchForm
from haystack.inputs import Exact
class FacetedProductSearchForm(FacetedSearchForm):
def __init__(self, *args, **kwargs):
data = dict(kwargs.get("data", []))
self.ptag = data.get('ptags', [])
self.q_from_data = data.get('q', '')
super(FacetedProductSearchForm, self).__init__(*args, **kwargs)
def search(self):
sqs = super(FacetedProductSearchForm, self).search()
# Ideally we would tell django-haystack to only apply q to destination
# ...but we're not sure how to do that, so we'll just re-apply it ourselves here.
q = self.q_from_data
sqs = sqs.filter(destination=Exact(q))
print('should be applying q: {}'.format(q))
print(sqs)
if self.ptag:
print('filtering with tags')
print(self.ptag)
sqs = sqs.filter(ptags__in=[Exact(tag) for tag in self.ptag])
return sqs
My search_indexes.py
import datetime
from django.utils import timezone
from haystack import indexes
from haystack.fields import CharField
from .models import Product
class ProductIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.EdgeNgramField(
document=True, use_template=True,
template_name='search/indexes/product_text.txt')
title = indexes.CharField(model_attr='title')
description = indexes.EdgeNgramField(model_attr="description")
destination = indexes.EdgeNgramField(model_attr="destination") #boost=1.125
link = indexes.CharField(model_attr="link")
image = indexes.CharField(model_attr="image")
# Tags
ptags = indexes.MultiValueField(model_attr='_ptags', faceted=True)
# for auto complete
content_auto = indexes.EdgeNgramField(model_attr='destination')
# Spelling suggestions
suggestions = indexes.FacetCharField()
def get_model(self):
return Product
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
return self.get_model().objects.filter(timestamp__lte=timezone.now())
My models.py
class Product(models.Model):
destination = models.CharField(max_length=255, default='')
title = models.CharField(max_length=255, default='')
slug = models.SlugField(unique=True, max_length=255)
description = models.TextField(max_length=2047, default='')
link = models.TextField(max_length=500, default='')
ptags = TaggableManager()
image = models.ImageField(max_length=500, default='images/zero-image-found.png')
timestamp = models.DateTimeField(auto_now=True)
def _ptags(self):
return [t.name for t in self.ptags.all()]
def get_absolute_url(self):
return reverse('product',
kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super(Product, self).save(*args, **kwargs)
def __str__(self):
return self.destination
And what I have in my views.py
from haystack.generic_views import FacetedSearchView as BaseFacetedSearchView
from .forms import FacetedProductSearchForm
class FacetedSearchView(BaseFacetedSearchView):
form_class = FacetedProductSearchForm
facet_fields = ['ptags']
template_name = 'search_result.html'
paginate_by = 30
context_object_name = 'object_list'
Thank you.
I just found the solution to this problem. Please listen up if you want to avoid loosing any of you reputations by placing a bounty on the same problem.
Basically I had to replace my original destination field in my search_indexes.py document to the following line:
From this: destination = indexes.EdgeNgramField(model_attr="destination")
To this: destination = indexes.CharField(model_attr="destination")
Your issue is in your use of dict.get
self.q_from_data = data.get('q', [''])[0]
For example
data.get('q') # This will return the string "Mexico"
data.get('q')[0] # This will return the first letter "M"
The line should be
self.q_from_data = data.get('q', '')
Related
Here is the case, I need the last records of the model to be displayed on the page, for this I added a new pub_date record in the models to add to the queue of records, I also added this to views.py, and it kind of displays, but both records.
views.py code
class IndexView(generic.ListView):
template_name = 'Homepage/index.html'
model = Goods
context_object_name = 'goods'
def description(self):
return self.description_text
def price(self):
return self.price_text
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
numbers = Number.objects.all()
context['numbers'] = numbers
return context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
avaibilitys = Avaibility.objects.order_by('-pub_date')
context['avaibilitys'] = avaibilitys
return context
models.py code
class Avaibility(models.Model):
name_text = models.CharField(max_length=200)
apply_text = models.CharField(max_length=200)
presence_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published', null=True)
def __str__(self):
return self.name_text
def __str__(self):
return self.apply_text
def __str__(self):
return self.presence_text
this is what displays
You are just sorting the data using order_by and assign the sorted data to a variable:
avaibilitys = Avaibility.objects.order_by('-pub_date')
If you want to get only one of them, you can do this:
avaibilitys = Avaibility.objects.order_by('-pub_date').first()
EDIT
If you want the last one, do this:
avaibilitys = Avaibility.objects.order_by('-pub_date').last()
Have two models - Program and Segments. I need to calculate the total times in the program entry from the fields within the associated Segments. I attempted to do that by overriding the save methods, but when entering a new segment it won't update the program model entries unless I go directly into the program form and save/update it.
I am missing how to get the segment Update to cause the Program Save/Update to happen.
How do I give it the context to call the program save method within the Segment update (After the segment has been saved).
Code of the models is:
from django.db import models
from django.urls import reverse
from datetime import datetime, timedelta
class Program(models.Model):
air_date = models.DateField(default="0000-00-00")
air_time = models.TimeField(default="00:00:00")
service = models.CharField(max_length=10)
block_time = models.TimeField(default="00:00:00")
block_time_delta = models.DurationField(default=timedelta)
running_time = models.TimeField(default="00:00:00")
running_time_delta = models.DurationField(default=timedelta)
remaining_time = models.TimeField(default="00:00:00")
remaining_time_delta = models.DurationField(default=timedelta)
title = models.CharField(max_length=190)
locked_flag = models.BooleanField(default=False)
deleted_flag = models.BooleanField(default=False)
library = models.CharField(null=True,max_length=190,blank=True)
mc = models.CharField(null=True,max_length=64)
producer = models.CharField(null=True,max_length=64)
editor = models.CharField(null=True,max_length=64)
remarks = models.TextField(null=True,blank=True)
audit_time = models.DateTimeField(null=True)
audit_user = models.CharField(null=True,max_length=32)
def calculate_time(self):
total_run_time_delta = timedelta(minutes=0)
for segs in self.segments.all():
total_run_time_delta += segs.length_time_delta
self.running_time_delta = total_run_time_delta
self.running_time = f"{self.running_time_delta}"
hold_time = self.block_time.strftime("%H:%M:%S")
t = datetime.strptime(hold_time,"%H:%M:%S")
self.block_time_delta = timedelta(hours=t.hour,
minutes=t.minute,seconds=t.second)
self.remaining_time_delta = self.block_time_delta - total_run_time_delta
self.remaining_time = f"{abs(self.remaining_time_delta)}"
def save(self, *args, **kwargs):
self.calculate_time()
super().save(*args,**kwargs)
def __str__(self):
return f"{self.pk} : {self.title}"
def get_absolute_url(self):
return reverse('program_detail', args=[str(self.id)])
#return reverse('program-update', kwargs={'pk': self.pk})
class Segment(models.Model):
program_id = models.ForeignKey(Program,
on_delete=models.CASCADE,
related_name='segments', #new link to Program
)
sequence_number = models.DecimalField(decimal_places=2,max_digits=6,default="0.00")
title = models.CharField(max_length=190)
bridge_flag = models.BooleanField(default=False)
length_time = models.TimeField(null=True,default=None, blank=True)
length_time_delta = models.DurationField(default=timedelta)
author = models.CharField(max_length=64,null=True,default=None,blank=True)
voice = models.CharField(max_length=64,null=True,default=None,blank=True)
library = models.CharField(max_length=190,null=True,default=None,blank=True)
summary = models.TextField()
audit_time = models.DateTimeField(null=True)
audit_user = models.CharField(null=True,max_length=32)
def save( self, *args, **kwargs):
super().save(*args,**kwargs)
return super(Program,self.program_id).save()
def __str__(self):
return f"{self.title}"
The views look like this...
class ProgramUpdateView(LoginRequiredMixin,UpdateView):
class Meta:
model = Program
widgets = {
'remarks': Textarea(attrs={'row':10, 'cols':80}),
}
model = Program
success_url = "/program/{id}/"
template_name = 'program_update.html'
fields = [
'title',
'service',
'library',
'air_date',
'air_time',
'producer',
'editor',
'mc',
'block_time',
'remaining_time',
'running_time',
'remarks',
]
def form_valid(self, form):
return super(ProgramUpdateView, self).form_valid(form)
class SegmentUpdate(LoginRequiredMixin,UpdateView):
model = Segment
fields = '__all__'
template_name = 'segment_update.html'
I originally thought I could do this all in the models, but now I am not so sure .
Thanks for any info you can provide....
try to directly call Program.save() method through the fk
in Segment model
def save( self, *args, **kwargs):
super().save(*args,**kwargs)
self.program_id.save()
or use django signals https://docs.djangoproject.com/en/3.1/topics/signals/
from django.db.models.signals import post_save, post_delete
#receiver([post_save, post_delete], sender=Segment)
def update_program(sender, instance, **kwargs):
program = Program.objects.get(pk=instance.program_id.pk)
program.save()
Please keep your database atomic. Don't save in it something that can be computed from other fields unless you have a very good reason to do it. The reason you're giving for doing it doesn't seem like a good one.
You want the total time of the segments when you got a list of programs ? Fine, simply annotate the querystring with a sum. You'll do it everytime ? Create a custom queryset/manager that do it for you.
For my website, I want users to be able to add tags to their posts. But I get this error:
python name 'Tag' is not defined
Here is some code
Relevant code in models.py
from taggit.managers import TaggableManager
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(max_length=75)
text = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
image = models.ImageField(upload_to='post_images',blank=True,null=True)
published_date = models.DateTimeField(blank=True,null=True,auto_now_add=True)
NSFW = models.BooleanField(default=False)
spoiler = models.BooleanField(default=False)
tags = TaggableManager()
def __str__(self):
return self.title
def save(self, *args, **kwargs):
super().save(*args, **kwargs)
Here is the relevant code in views.py
class TagMixin(object):
def get_context_data(self,**kwargs):
context = super(TagMixin,self).get_context_data(**kwargs)
context['tags'] = Tag.objects.all()
return context
class PostListView(TagMixin,ListView):
template_name = 'mainapp/post_list.html'
model = Post
context_object_name = 'posts'
queryset = Post.objects.all()
def get_queryset(self):
return Post.objects.filter(published_date__lte=timezone.now()).order_by('-published_date')
class TagIndexView(TagMixin,ListView):
template_name = 'mainapp/post_list.html'
context_object_name = 'posts'
model = Post
def get_queryset(self):
return Posts.objects.filter(tags__slug=self.kwargs.get('slug'))
And here is the form.
class PostForm(forms.ModelForm):
class Meta():
model = Post
fields = ['title','text','image','tags','spoiler','NSFW']
widgets = {
'title':forms.TextInput(attrs={'class':'textinputclass'}),
'text':forms.Textarea(attrs={'class':'textareaclass editable'}),
}
def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.fields['image'].required = False
I am getting the error in the Mixin, on this line python context['tags'] = Tag.objects.all()
Can anyone tell me why I am getting an error of python name 'Tag' is not defined
So far I have changed the casing of the word, I have changed the name, but none of it works.
Thank you for any help you can give :)
I'm trying to make the suggestions from Django-haystack' autocomplete to be sensitive to words containing accents. (For french language)
Current result:
User type Seville
Output suggestion returns nothing because the actual destination name is Séville
Expected result:
User type Seville
Output suggestion returns Séville
I have read the following documentation but I'm still unsure on how to achieve this: https://django-haystack.readthedocs.io/en/master/searchqueryset_api.html#order-by
Here's my code:
Forms.py
from haystack.forms import FacetedSearchForm
from haystack.inputs import Exact
class FacetedProductSearchForm(FacetedSearchForm):
def __init__(self, *args, **kwargs):
data = dict(kwargs.get("data", []))
self.ptag = data.get('ptags', [])
self.q_from_data = data.get('q', '')
super(FacetedProductSearchForm, self).__init__(*args, **kwargs)
def search(self):
sqs = super(FacetedProductSearchForm, self).search()
# Ideally we would tell django-haystack to only apply q to destination
# ...but we're not sure how to do that, so we'll just re-apply it ourselves here.
q = self.q_from_data
sqs = sqs.filter(destination=Exact(q))
print('should be applying q: {}'.format(q))
print(sqs)
if self.ptag:
print('filtering with tags')
print(self.ptag)
sqs = sqs.filter(ptags__in=[Exact(tag) for tag in self.ptag])
return sqs
search_indexes.py
import datetime
from django.utils import timezone
from haystack import indexes
from haystack.fields import CharField
from .models import Product
class ProductIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.EdgeNgramField(
document=True, use_template=True,
template_name='search/indexes/product_text.txt')
title = indexes.CharField(model_attr='title')
description = indexes.EdgeNgramField(model_attr="description")
destination = indexes.EdgeNgramField(model_attr="destination") #boost=1.125
link = indexes.CharField(model_attr="link")
image = indexes.CharField(model_attr="image")
# Tags
ptags = indexes.MultiValueField(model_attr='_ptags', faceted=True)
# for auto complete
content_auto = indexes.EdgeNgramField(model_attr='destination')
# Spelling suggestions
suggestions = indexes.FacetCharField()
def get_model(self):
return Product
def index_queryset(self, using=None):
"""Used when the entire index for model is updated."""
return self.get_model().objects.filter(timestamp__lte=timezone.now())
Models.py
class Product(models.Model):
destination = models.CharField(max_length=255, default='')
title = models.CharField(max_length=255, default='')
slug = models.SlugField(unique=True, max_length=255)
description = models.TextField(max_length=2047, default='')
link = models.TextField(max_length=500, default='')
ptags = TaggableManager()
image = models.ImageField(max_length=500, default='images/zero-image-found.png')
timestamp = models.DateTimeField(auto_now=True)
def _ptags(self):
return [t.name for t in self.ptags.all()]
def get_absolute_url(self):
return reverse('product',
kwargs={'slug': self.slug})
def save(self, *args, **kwargs):
if not self.id:
self.slug = slugify(self.title)
super(Product, self).save(*args, **kwargs)
def __str__(self):
return self.destination
Finally, in my views.py:
from haystack.generic_views import FacetedSearchView as BaseFacetedSearchView
from .forms import FacetedProductSearchForm
from haystack.query import SearchQuerySet
def autocomplete(request):
sqs = SearchQuerySet().autocomplete(
content_auto=request.GET.get('query',''))[:5]
destinations = {result.destination for result in sqs}
s = [{"value": dest, "data": dest} for dest in destinations]
output = {'suggestions': s}
return JsonResponse(output)
class FacetedSearchView(BaseFacetedSearchView):
form_class = FacetedProductSearchForm
facet_fields = ['ptags']
template_name = 'search_result.html'
paginate_by = 30
context_object_name = 'object_list'
Any ideas on how to achieve this?
I hava an Article model ,contains a title column,which can be stored mix with white space,what i want is that ,every time i query an article,space in title content could be repaced with dash,for url friendly.
models.py:
class Article(models.Model):
STATUS = (
(0,'on'),
(1,'off')
)
#id = models.IntegerField(primary_key=True,help_text='primary key',auto_created=True)
category = models.ForeignKey(Category,related_name='articles', help_text='foreigner key reference Category')
#author = models.ForeignKey(myadmin.User, help_text='foreigner key reference myadmin User')
title = models.CharField(max_length=100, help_text='article title')
description = models.TextField(help_text='article brief description')
content = models.TextField(help_text='article content')
like = models.IntegerField(default=0,help_text='like numbers')
secretcode = models.CharField(max_length=512,help_text='who has the code can scan')
status = models.IntegerField(choices=STATUS,help_text='status of the article')
createtime = models.DateTimeField(auto_now_add=True,help_text='time that first created')
modifytime = models.DateTimeField(auto_now=True,help_text='time when modified')
articles = models.Manager()
def __str__(self):
return self.title
class Meta:
db_table = 'article'
my view.py:
def get(self,request):
offset = int(request.GET.get('offset', 0))
category = request.GET.get('category')
end = offset+10
articlecatalogs = Article.articles.filter(category__name=category)[offset:end]
i was thinking creating a custom Manager and define a method to transform the data,but the query conditions needed are from request,in here,i don't know how to do it ?can someone help me?
I think you have to use slug filed as well and overwrite your save method for save slug something like this :
class Article(models.Model):
slug = models.SlugField("slug")
category = models.ForeignKey(Category,related_name='articles', help_text='foreigner key reference Category')
-- more fields --
def __str__(self):
return self.title
def get_absolute_url(self):
return self.slug
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.title.strip(" ").replace(' ','-')
super(Article, self).save(self, *args, **kwargs)
#property
def get_title(self):
""" write python code for remove extra spaces so can can dispay your tile in html and call this method with instance when you want to print title """
return new_title_without_extra_spaces
for details page you can use slug value for get a instance. Hope this would be helpful to you.