views:

95

answers:

1

I have setup like so (changed for simplicity)

class Author(models.Model)
    name = models.CharField(max_length=100)
    ...

class Document(models.Model):
    title = models.CharField(max_length=200)
    content - models.TextField()
    author = models.ForeignKey("Author", related_name="documents")
    date_published = models.DateTimeField()
    categories = models.ManyToManyField("Category")

class Category(models.Model):
    name = models.CharField(max_length=100)

I'm pulling in the Author records but I only want to pull in related document records for each author that match specific criteria -- say, date_published and category.

I know the easy way to do this would be to pull in the records as a list of dictionaries using Author.objects.values(), looping through each record and running:

author['documents']=Document.objects.filter(categories__in=[category_list], date_published__year=year)`

However, this is being generated for django-piston, and it seems to be happiest (particularly if you're defining your own fields!) if you return a QuerySet object.

Part of this may be because I made changes to the base django-piston code. Basically, the current version of the code here overwrites the fields value. I changed this code so that I could change the fields value for a Handler based on the request (so I could provide more details if the request was for a specific resource).

So I guess my question is three-fold:

  1. Is there a way to filter or somehow limit the subrecords of a record (i.e. filter documents for each author.documents)
  2. If not, what is a functional way of doing this that also works with django-piston?
  3. Is there some easier, better way to do what I'm trying to do (display all the authors without their documents if an id is not given, but displaying the sub-records if filtering to just one author)?

Clarification

Okay, just to be clear, here is the pseudocode that I want:

def perhaps_impossible_view(request, categories=None, year=None):
    authors = Author.objects.all()
    authors.something_magical_happens_to_documents(category__in=[categories], date_published__year=year)
    return render_to_response('blar.html', locals(), RequestContext(request))

So that if I were to put it in a template, this would work without any modifications:

{% for a in authors %}
    {% for d in authors.documents.all %}
        {{ d.title }} is almost certainly in one of these categories: {{ categories }} and was absolutely published in {{ year }}. If not, .something_magical_happens_to_documents didn't work.
    {% endfor %}
{% endfor %}

something_magical_happens_to_documents has to run and actually change the contents of documents for each author record. It seems like this should be possible, but perhaps it isn't?

A: 

Edited... or better... replaced! :)

That's true the authors without document matching won't be in the queryset so you will have to add them back after (I couldn't find a better way but maybe someone knows how to not remove them without using raw sql).

You get the full documents count of the authors because you don't use the queryset to get the document counts:

qryset = Author.objects.all()
qryset = qryset.filter(documents__categories__name__in = category_list).distinct()
qryset = qryset.filter(documents__date_published__year=year)

print(qryset) gives [<Author: Author object>] (if only 1 author matched all categories) and qryset[0].documents.count() will return only the number of documents matched (not all documents from the author - 2 in the case I tested and the author had 4 but only 2 matching all conditions).

If you use dict (.values()) instead of querysets in the steps above, you can't do that (I think) because dict won't have a documents field so:

qryset_dict = Author.objects.values()
qryset_dict = qryset_dict.filter(documents__categories__name__in = category_list).distinct().values()
qryset_dict = qryset_dict.filter(documents__date_published__year=year).values()

when you issue qryset_dict[0].documents.count() you receive an error:

AttributeError: 'dict' object has no attribute 'documents'


Now to add the filtered authors back you can do:

res = []
for a in Author.objects.all():
    if a in qryset:
        res.append([a,a.documents.count()])
    else:
        res.append([a,0])

and res will be a list with <authors> in 1st column and count of documents matching in 2nd column.

I know this is far from perfect... but if you are interested only in the count() of matching documents per author, I think you could find a better way using django aggregation and annotation or possibly make it with raw sql using a left join from authors to documents.




EDIT after Clarification in Question:

def possible_view(request, categories=None, year=None):
    # you will pass these as parmeters of course
    category_list = ['c2', 'c3']
    year = 2010

    qryset = Document.objects.filter(categories__name__in = category_list).distinct()
    qryset = qryset.filter(date_published__year=year)
    authors = Author.objects.all()
    return render_to_response('blar.html', { 'result': qryset, 'authors': authors, 'categories': category_list, 'year': year }, RequestContext(request))

Template blar.html:

{% for a in authors %}
    <b>{{a.name}}</b><br />
    {% for d in result %}
        {% if d.author.name == a.name %}
            {{ d.title }} is almost certainly in one of these categories: {{ categories }} and was absolutely published in {{ year }}. If not, .something_magical_happens_to_documents didn't work.<br />
        {% endif %}
    {% endfor %}<br />
{% endfor %}

This will give you something not very pretty but with all authors and below each one, the list of their documents that fall within one of the category_list (OR condition, for AND, you need to filter the query for each category instead of using __in).

If the author has no document in the category_list, it wil be listed without documents below him.


Something like:

aut1
tit2 is almost certainly in one of these categories: ['c2', 'c3'] and was absolutely   published in 2010. If not, .something_magical_happens_to_documents didn't work.
tit1 is almost certainly in one of these categories: ['c2', 'c3'] and was absolutely published in 2010. If not, .something_magical_happens_to_documents didn't work.

aut2
tit3 is almost certainly in one of these categories: ['c2', 'c3'] and was absolutely published in 2010. If not, .something_magical_happens_to_documents didn't work.

aut3
laurent-rpnet
This only filters the Author records, which is not the desired effect. John, Sally, and Maria each write 100 docs, John has 10 matching documents, Sally 8, Maria 0. With your query, only John and Sally would come up, and for each `author.documents.count()` would still be 100. I want John, Sally, and Maria to come up, and `author.documents.count()` should be 10, 8, and 0, respectively. I suspect that there is a hack that allows you to set this if you are willing to mess with the internals of a QuerySet object.
Jordan Reiter
@Jordan, True for the authors without matches missing (it's a filter so they are removed of course) but the document count is not working because you are not using the queryset to find the document count. As the comment is too long and needs code, I edited the answer.
laurent-rpnet
@Jordan, `author.documents.count()` will always return 100 unless you modify the data as it is a new query without filtering
laurent-rpnet
"Jordan, author.documents.count() will always return 100 unless you modify the data as it is a new query without filtering" Yeah, that's exactly why I'm wondering if there's some workaround to filter just the children and not the parents. Right now it looks like the only option is to do some kind of loop and put it all in a dictionary or something.
Jordan Reiter
Also, I don't really care about the count, except insofar as the count **should be** `x` where x is whatever number of matching records it should be. I might have written "...and author.documents.all() should have 10, 8, and 0 records respectively" to make it clearer. Also, if you're using the qryset_dict code there is zero reason for filtering it, as I do not want to filter Authors, only the author's documents. I'm going to edit my question to make what I want even more clear.
Jordan Reiter
OK - got it - The template is wrong and also I thought you were interested in counts - Edited answer above to reflect your clarification.
laurent-rpnet
Regarding filtering the children, I don't know any way to do it but I think you never need that because you don't need the parent in the query (I did this way in the edit) as a children will always have a way to acces his parent using the FK so you can work on the children only and get the parent when needed so the children becames the parent for the query and you can apply the filter to it.
laurent-rpnet
The outer loop (authors) in the template in my last edit is only used to present the results as you can get the author with `d.author.name` from the `document` queryset.
laurent-rpnet
I'm sort of suffering an impossible-to-explain-without-making-more-complicated type situation. Basically, this must be a queryset, and just a queryset, so I'm guessing there's nothing to be done. I'm not using a template, I'm not even using a view. The stuff in the template is again just to show what I want the queryset to contain, not to describe what I want to do with that queryset. I want the queryset to exist in such a manner that running the template code **as-is** will output what I want. Does that make sense?
Jordan Reiter
I'm just going to regard this as an unsolveable problem. :(
Jordan Reiter
not yet... ;) interesting problem... How will you use the queryset? Anyways, you want 1 single queryset with all `authors` even those with no documents matching `category_list` and a "sub-queryset" (as `author.documents`) for each author with only his `documents` matching` the `category_list`? right?
laurent-rpnet