While going through the Django tutorial, I've decided to implement some "ideas for more tests" in part 5, namely
For example, it’s silly that Questions can be published on the site that have no Choices. So, our views could check for this, and exclude such Questions.
I've implemented this check in views.py by changing the IndexView.get_queryset function as follows
def get_queryset(self):
#Return the last five published questions with nonzero choice_set
result = [];
for q in Question.objects.all():
if q.choice_set.count() > 0:
result.append(q)
return sorted( result, key=attrgetter('pub_date'), reverse=True )[0:5]
Is there a more elegant way of doing this? Something along the lines of return Question.objects.filter(choice_set.count()>0).order_by('-pub_date')[:5] (which does not work for probably obvious reasons).
EDIT 1
Another possible answer would be return Question.objects.filter(pk__in=[x.question.pk for x in Choice.objects.all()]).order_by('-pub_date')[:5] (inspired by this). It still won't allow exclusion of questions with a single choice, like #catavaran suggested.
You can exclude all questions where choice=None, and it will exclude all questions without choices:
qs = Question.objects.exclude(choice=None).order_by'(-pub_date')[:5]
Or you can use choice__isnull=False, but this can return duplicate entries. Use .distinct() to counter that:
qs = Question.objects.filter(choice__isnull=False).distinct()
You can use aggregation:
from django.db.models import Count
Question.objects.annotate(num_choices=Count('choice')) \
.filter(num_choices__gt=0).order_by('-pub_date')[:5]
In fact poll with only one choice does not makes sense too :-) So it is better to use filter(num_choices__gt=1) instead of filter(num_choices__gt=0).
Related
I've followed django tutorial and arrived at tutorial05.
I tried to not show empty poll as tutorial says, so I added filter condition like this:
class IndexView(generic.ListView):
...
def get_queryset(self):
return Question.objects.filter(
pub_date__lte=timezone.now(),
choice__isnull=False
).order_by('-pub_date')[:5]
But this returned two objects which are exactly same.
I think choice__isnull=False caused the problem, but not sure.
choice__isnull causes the problem. It leads to join with choice table (to weed out questions without choices), that is something like this:
SELECT question.*
FROM question
JOIN choice
ON question.id = choice.question_id
WHERE question.pub_date < NOW()
You can inspect query attribute of QuerySet to be sure. So if you have one question with two choices, you will get that question two times. You need to use distinct() method in this case: queryset.distinct().
Just use .distinct() at the end of your ORM.
A little late to the party, but I figured it could help others looking up the same issue.
Instead of using choice__isnull=False with the filter() method, use it with exclude() instead to exclude out any questions without any choices. So your code would look something like this:
...
def get_queryset(self):
return Question.objects.filter(pub_date__lte=timezone.now()).exclude(choice__isnull=True).order_by('-pub_date')[:5]
By doing it this way, it will return only one instance of the question. Be sure to use choice_isnull=True though.
Because you created two objects with same properties. If you want to ensure uniqueness, you should add validation in clean and add unique index on identifier field too.
Besides filter returns all the objects that match the criteria, if you are expecting only one item to be returned, you should use get instead. get would raise exception if less or more than 1 item is found.
Suppose I wanna run the exclude command repeatedly getting variables from exclude_list e.g. ['aa', 'ab' 'ac'].
I can do that using a loop:
for exclude_value in exclude_list:
myQueryset.exclude(variable__startswith=exclude_value)
However, I'd like to do that using the itertools.chain command as I've read it is capable of doing so. Any suggestions?
What you are doing is the correct approach - except for one small detail, you aren't retaining the excludes. Django querysets are lazily evaluated, so running through a loop and continually chaining won't do anything, right up until you try to access something from the set.
If you do this:
qs = MyModel.objects
for exclude_value in exclude_list:
qs = qs.exclude(variable__startswith=exclude_value)
qs = None
The database is never hit.
So do this:
qs = MyModel.objects
for exclude_value in exclude_list:
qs = qs.exclude(variable__startswith=exclude_value)
qs.count() # Or whatever you want the queryset for
and you should be fine, if/when you are experience database slowdown, which ill likely be because of the large number of freetext expressions in a query, then do some profiling, then you can find an efficiency.
But I'd wager the above code would be sufficient for your needs.
I have two models Post and Comment. Both can be given a Point through an extra ratings app. The Point has a generic relationship to any object necessary and a value.
Now I've created a list of Post and Comment objects using:
result_list = sorted(chain(post_list, comment_list),key=attrgetter('pub_date',))
The big question is: How can I sort by assigned Points instead of pub_date?
I'm looking for a Solution that does not modify Post, Comment or Point, because Point is in an extra app and I want all of them to work independent of each other. There is a third app that combines them though, so I can have a new view that does the trick. But I don't know how to do that.
something along the line of the following should do the trick:
result_list = sorted(
chain(post_list, comment_list),
key=lambda x: sum(
[
point.value for point in Point.objects.filter(
point_for_id=x.id,
point_for_class=ContentType.objects.get_for_model(x)
],
0
)
)
May I however suggest that you man up and modify the points app, a related_query_name would make this much simpler.
I have a Django app that searches for data from a model Classified and displays them using simple querysets based on the input term. This works perfectly and I've got no complains with this method.
However, if someone enters a term that returns no data, I would like to provide an option with alternate/suggested search.
Eg: Someone searches 'Ambulance Services' which doesn't return data. I'd like to suggest 'Ambulance' or 'Services' as alternate search options which may return data from the model depending on the data present in the model.
I Googled suggested search in django and it gave me options of Haystack/elastic search, etc which I'm not sure are really required since the search is across just one model.
Note: SO tells me that my question may be closed as it is subjective. If thats the case, please suggest where I can move it to. Thank you!
This is just an idea, but might work for you:
The user enters the search data: "Ambulance Services"
If the query inside the view returns nothing, redirect to the same view using your selected alternative search data, lets say "Ambulance", and a flag value thats saids the view you're preforming a suggested search.
You must have two things in consideration:
What if the alternate search don't returns anything either? You have to set a recursion limit here.
How I'm going to select the data of the alternate search? Well, thats another question about a completly different topic.
This is this idea in code:
def search(request, data, alternate=False, recursion_level=3):
result = Classified.objects.filter(some_filed=data)
if 0 == result.count() and 0 != recursion_level: # Conditions needed for perform alternate search.
new_data = select_new_data(data) # The logic inside this function is up to you.
r_level = recursion_level - 1 # Decreas recursion level.
return redirect('search', alternate=True, recursion_level=r_level) # Redirecting using view name, you can use others
# forms of redirection see link below for visit
# the Djando redirect API doc.
else:
return_data = {}
if alternate:
# You can use this flag in order to reflect
# to the user you have performed an alternate search.
# Example:
return_data["alternate"] = True
# Build your return data.
# and render.
#return render_to_template()
Django redirect doc: redirect
Haystack is, indeed, a great option, here you will find how to give 'spelling suggestions', you can see an example in this OS question/answer
No matter you have only one model, this tool is really great, simple to install /set up/use, and very flexible.
Perhaps this helps as well.
I have to querysets. alllists and subscriptionlists
alllists = List.objects.filter(datamode = 'A')
subscriptionlists = Membership.objects.filter(member__id=memberid, datamode='A')
I need a queryset called unsubscriptionlist, which possess all records in alllists except the records in subscription lists. How to achieve this?
Since Django 1.11, QuerySets have a difference() method amongst other new methods:
# Capture elements that are in qs_all but not in qs_part
qs_diff = qs_all.difference(qs_part)
Also see: https://stackoverflow.com/a/45651267/5497962
You should be able to use the set operation difference to help:
set(alllists).difference(set(subscriptionlists))
Well I see two options here.
1. Filter things manually (quite ugly)
diff = []
for all in alllists:
found = False
for sub in subscriptionlists:
if sub.id == all.id:
found = True
break
if not found:
diff.append(all)
2. Just make another query
diff = List.objects.filter(datamode = 'A').exclude(member__id=memberid, datamode='A')
How about:
subscriptionlists = Membership.objects.filter(member__id=memberid, datamode='A')
unsubscriptionlists = Membership.objects.exclude(member__id=memberid, datamode='A')
The unsubscriptionlists should be the inverse of subscription lists.
Brian's answer will work as well, though set() will most likely evaluate the query and will take a performance hit in evaluating both sets into memory. This method will keep the lazy initialization until you need the data.
In case anyone's searching for a way to do symmetric difference, such operator is not available in Django.
That said, it's not that hard to implement it using difference and union, and it'll all be done in a single query:
q1.difference(q2).union(q2.difference(q1))