rails4 // has_many_through syntax when adding relation - ruby-on-rails-4

How would one pass parameters to a has_many association ?
Let's say we have categories and posts, associated with a join table, category_posts
category has_many category_posts
category_has_many posts, through category_posts
and reverse for the post (not the right syntax, to make it quick here)
Let's say we got this basic syntax for adding :
category.posts << post
The join table category_posts has a specific field that I'd like to fill when creating the above association.
I'm looking for a one-liner syntax to do it, if that'd ever be possible
Thanks for your advance experience feedback
(totally different, but this is what I mean by "one-liner syntax" Rails4 // append strong_parameters with other params : the kind of thing that tend to be done with many more lines of code)

You'd have to do it by adding to the category_posts relation directly, e.g.:
category.category_posts << CategoryPost.create(post: post, other_field: other_value)
or, slightly more condensed:
category.category_posts.create(post: post, other_field: other_value)

Related

Exclude fields with the same value using Django querysets

In my project, I have Articles and User Responses that have the same value "title". I only want to find the first Articles, because other object have the same "title", these are the users' answers. How can I exclude objects from queryset have the same "title" parameter.
I try this:
q1 = Article.objects.order_by().values('title').distinct()
*works good but it returns something like a list.
Well, I tried to convert it to query:
q2 = Article.objects.filter(title__in=q1).distinct()
*But it causes it to return all Repeat-Topic Articles again.
How to exclude objects from queryset that have the same title without changing them to a list?
On PostgreSQL only, you can pass positional arguments (*fields) in order to specify the names of fields to which the DISTINCT should apply.
If it is your's case then the following must be work:
Article.objects.filter(title__in=q1).order_by('title').distinct('title')

Why .filter() in django returns duplicated objects?

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.

Django annotate a field value to queryset

I want to attach a field value (id) to a QS like below, but Django throws a 'str' object has no attribute 'lookup' error.
Book.objects.all().annotate(some_id='somerelation__id')
It seems I can get my id value using Sum()
Book.objects.all().annotate(something=Sum('somerelation__id'))
I'm wondering is there not a way to simply annotate raw field values to a QS? Using sum() in this case doesn't feel right.
There are at least three methods of accessing related objects in a queryset.
using Django's double underscore join syntax:
If you just want to use the field of a related object as a condition in your SQL query you can refer to the field field on the related object related_object with related_object__field. All possible lookup types are listed in the Django documentation under Field lookups.
Book.objects.filter(related_object__field=True)
using annotate with F():
You can populate an annotated field in a queryset by refering to the field with the F() object. F() represents the field of a model or an annotated field.
Book.objects.annotate(added_field=F("related_object__field"))
accessing object attributes:
Once the queryset is evaluated, you can access related objects through attributes on that object.
book = Book.objects.get(pk=1)
author = book.author.name # just one author, or…
authors = book.author_set.values("name") # several authors
This triggers an additional query unless you're making use of select_related().
My advice is to go with solution #2 as you're already halfway down that road and I think it'll give you exactly what you're asking for. The problem you're facing right now is that you did not specify a lookup type but instead you're passing a string (somerelation_id) Django doesn't know what to do with.
Also, the Django documentation on annotate() is pretty straight forward. You should look into that (again).
You have <somerelation>_id "by default". For example comment.user_id. It works because User has many Comments. But if Book has many Authors, what author_id supposed to be in this case?

Django order_by() filter with distinct()

How can I make an order_by like this ....
p = Product.objects.filter(vendornumber='403516006')\
.order_by('-created').distinct('vendor__name')
The problem is that I have multiple vendors with the same name, and I only want the latest product by the vendor ..
Hope it makes sense?
I got this DB error:
SELECT DISTINCT ON expressions must match initial ORDER BY expressions
LINE 1: SELECT DISTINCT ON ("search_vendor"."name")
"search_product"...
Based on your error message and this other question, it seems to me this would fix it:
p = Product.objects.filter(vendornumber='403516006')\
.order_by('vendor__name', '-created').distinct('vendor__name')
That is, it seems that the DISTINCT ON expression(s) must match the leftmost ORDER BY expression(s). So by making the column you use in distinct as the first column in the order_by, I think it should work.
Just matching leftmost order_by() arg and distinct() did not work for me, producing the same error (Django 1.8.7 bug or a feature)?
qs.order_by('project').distinct('project')
however it worked when I changed to:
qs.order_by('project__id').distinct('project')
and I do not even have multiple order_by args.
In case you are hoping to use a separate field for distinct and order by another field you can use the below code
from django.db.models import Subquery
Model.objects.filter(
pk__in=Subquery(
Model.objects.all().distinct('foo').values('pk')
)
).order_by('bar')
I had a similar issue but then with related fields. With just adding the related field in distinct(), I didn't get the right results.
I wanted to sort by room__name keeping the person (linked to residency ) unique. Repeating the related field as per the below fixed my issue:
.order_by('room__name', 'residency__person', ).distinct('room__name', 'residency__person')
See also these related posts:
ProgrammingError: when using order_by and distinct together in django
django distinct and order_by
Postgresql DISTINCT ON with different ORDER BY

How to search for objects without certain tags?

I have a queryset containing some objects. Depending on some case or the other i now want to exclude all the objects without certain tags (_tags is the name of the TagField on my model):
self.queryset=self.queryset.exclude(_tags__id__in=avoid)
But this just leaves me with an error:
Caught FieldError while rendering:
Join on field '_tags' not permitted.
Did you misspell 'id' for the lookup type?
As i'm pretty sure i did not misspell 'id', i did some searching on how to use tagging for something like this. In the docs there is a lot about custom Managers, but somehow i just can't get it how i can use them to get what i want.
edit:
corrected the code above to
self.queryset=self.queryset.exclude(_tags__in=avoid)
where avoid is a list of integers. And that leaves me with the problem that the TagField of django-tagging is just a special CharField (or TextField?). Which will, of course, not sort out anything if i just query it against a list of integers. I could try to solve this in a way like this:
for tag in avoid:
self.queryset=self.queryset.exclude(_tags__contains=tag.name)
which is not only ugly, but also leaves me with the problem of tags made of multiple words or matching parts of other tags.
I somehow have the suspicion that this could be solved in a much prettier way by someone who has understood how django-tagging works.
How are your models defined? Is _tags a ForeignKey field?
if not remove the __id part
self.queryset=self.queryset.exclude(_tags__in=avoid)
Unfortunately, no, there's no prettier way. In fact, the actual solution is even uglier, but when all the tags are stored in a single text field, there's no other way:
from django.db.models import Q
startswith_tag = Q(_tags__startswith=tag.name+' ')
contains_tag = Q(_tags__contains=' '+tag.name+' ')
endswith_tag = Q(_tags__endswith=' '+tag.name)
self.queryset=self.queryset.exclude(startswith_tag | contains_tag | endswith_tag)
The code above assumes that tags are delimited with spaces. If not, you'll have to modify the code to match how they are delimited. The idea is that you use the delimiter as part of the search to ensure that it's the actual tag and not just part of another tag.
If you don't want to do it this way, I'd suggest switching to another tag system that doesn't dump them all into a single text field, django-taggit for instance.
As described in the comment on Chris' answer, django-tagging does not deliver the tagstring when accessing model._tag. In the end i had no other solution than to do the query and sort out the loops containing a certain tag afterwards:
itemlist = list(queryset)
avoid = some_list_of_tag_ids
# search for loops that have NONE of the avoid tags
for item in itemlist:
# has tags and [ if a tag.id in avoid this list has an element]
if (item.tags) and [tag for tag in item.tags if tag.id in avoid]:
# remove the item from the list
itemlist.remove(item)
To complete that the model for this looks like this:
class Item(models.Model):
_tags = TagField(blank=True,null=True)
def _get_tags(self):
return Tag.objects.get_for_object(self)
def _set_tags(self, tags):
Tag.objects.update_tags(tags)
tags = property(_get_tags, _set_tags)
Allthough i tried for quite a while, i found no way of chaining a query against tagging tags into a query chain for an object. For this project I'm stuck with tagging, but this is a real drawback...