QuerySet Question

1 view
Skip to first unread message

Chris

unread,
Nov 12, 2009, 10:12:39 AM11/12/09
to Django users
Hi all,

I am new to Django and I am having a little trouble.

The situation:
I am setting up a music selling website (similar to iTunes) for a uni
project. There are Tracks, Albums and Artists as well as users via
Django auth. A user can own a track. This is accomplished with a many
to many field in the Track model (owners = models.ManyToManyField
(User)).

The problem:
I want to put an "owned" tick next to each track that the user owns
throughout the site.

I cannot put a function in the Track class to return whether the
current user owns the track or not because track does not have access
to the user details (request.user).

I could return another data set with the same indices as the tracks
and have it store a boolean to indicate whether the current user owns
the track or not. Something along the lines of "owner[i] = tracks
[i].owners.filter(username__exact=request.user.username).count()".
Alternatively, do something like "Track.objects.extra(select={'owned':
"1"})" with some logic in there to figure out if it should be 1 or 0
(anyone know how to do something like that???). The trouble with
either of these methods, aside from not being very elegant, is that I
don't always just pass a tracks QuerySet to a template. I often pass
an albums QuerySet or an artists QuerySet, both of which are also used
to access tracks. For the case of a set of albums I would need to
iterate over all of them, iterate over all of their tracks and store
which tracks are owned in a 2D array. For the case of a set of artists
I would need to iterate over all of them, iterate over all their
albums, iterate over all of their tracks and store which ones are
owned in a 3D array. There has got to be a better way than this.

Is there a way of getting a model function to access user details so
that I can do a simple "if track.owned" in the template? Is there a
better solution to doing it in the view? Is there something I have not
thought of? What would be the "best practice" solution to this?

Thanks in advance for your help.

Tomasz Zieliński

unread,
Nov 12, 2009, 3:12:02 PM11/12/09
to Django users
On 12 Lis, 16:12, Chris <chrisbrett...@googlemail.com> wrote:
> Is there a way of getting a model function to access user details so
> that I can do a simple "if track.owned" in the template? Is there a
> better solution to doing it in the view? Is there something I have not
> thought of? What would be the "best practice" solution to this?
>

Just some quick ideas (after 10h of work, so watch out ;)):

1. You can write custom tag {%ifowned track request.user%}

2. You can try something less elegant:

{%for user in track.m2m_users.all%}
{%ifequal user request.user%}
<do what you want here>
{%endifequal%}
{%endfor%}

- but it's only good for small apps, as a temp solution.

--
Tomasz Zieliński
http://pyconsultant.eu

Chris

unread,
Nov 13, 2009, 10:01:29 AM11/13/09
to Django users
Thanks for your reply.

From reading around it sounds as though I am probably not meant to
solve this in the template (though this does seem like the easier
option).

I have figured out that I could solve this through an SQL statement as
follows:

select *, (select Count(*) FROM shop_track_owners where
track_id=shop_track.id and user_id=<<<insert current user id
here>>>)"owned" from shop_track;

I have run this and it works. There are two problems though:

1: How do I do this through django querysets? I would like to keep the
database abstracted.

2: This will probably be rather awkward to do when talking about
artists or albums... Could I somehow modify the .track_set function of
the album model to not just return tracks (select * from shop_track)
but to run my other funky query?

Also I have had another thought: I could add an "owned" field to the
Track model. This could remain null for all tracks in the database
forever, but could be used in the view when grabbing the tracks to
pass the template whether or not each track is owned by the current
user. For example:

tracks = Track.objects.all()
for track in tracks:
if request.user in track.owners:
track.owned = True

Then render to response. Again, a couple of potential problems:

1: Does altering the track object (track.owned = True) also alter the
object within tracks? I don't know if it a reference or a copy. If it
is a copy, is there any way of shoving it back in the queryset?

2: There is still the problem of what to do when talking about albums.
Could you do something like:

for album in albums:
for track in album.track_set():
if request.user in track.owners:
track.owned = True

Again, would setting track.owned = True affect the track within the
album's track_set()? I would assume not... I think album.track_set()
executes a new database query to get the set, right? Is there any way
around this?

One last possibility that I can think of: is there any way of allowing
the track model to get at the request object? This would allow me to
write a method in the track model (owned) that checks if the track is
owned by the user in the request and then returns a bool.

Thanks again.

On Nov 12, 8:12 pm, Tomasz Zieliński

Matt Schinckel

unread,
Nov 13, 2009, 5:19:00 PM11/13/09
to Django users
On Nov 14, 1:01 am, Chris <chrisbrett...@googlemail.com> wrote:
> Thanks for your reply.
>
> From reading around it sounds as though I am probably not meant to
> solve this in the template (though this does seem like the easier
> option).
>
> I have figured out that I could solve this through an SQL statement as
> follows:
>
> select *, (select Count(*) FROM shop_track_owners where
> track_id=shop_track.id and user_id=<<<insert current user id
> here>>>)"owned" from shop_track;
>
> I have run this and it works. There are two problems though:
>
> 1: How do I do this through django querysets? I would like to keep the
> database abstracted.

You can execute arbitrary SQL like this using the extra method on a
queryset. Something like:

qs.extra(select={'count':'SELECT count(*) FROM shop_track_owners where
track_id=shop_track.id and user_id=%i' % user.id)
You can't always assume there is a request object: for instance any
interaction with the system through ./manage.py shell doesn't have a
request object. Also, consider that there may be many request objects
in the system at one time, if many users are using the system
simultaneously.

This is something that belongs in the view, and probably no lower, as
that is the last place where there is _always_ a request object.

Having said that, you could do something like this in the model:

def owned(self, request):
"See if the user of this request owns this object"
return request.user in self.track_owners

This then means you can call that method from anywhere, and pass in
the current request object.
> Thanks again.
>
> On Nov 12, 8:12 pm, Tomasz Zieliñski
>
>
>
> <tomasz.zielin...@pyconsultant.eu> wrote:
> > On 12 Lis, 16:12, Chris <chrisbrett...@googlemail.com> wrote:
>
> > > Is there a way of getting a model function to access user details so
> > > that I can do a simple "if track.owned" in the template? Is there a
> > > better solution to doing it in the view? Is there something I have not
> > > thought of? What would be the "best practice" solution to this?
>
> > Just some quick ideas (after 10h of work, so watch out ;)):
>
> > 1. You can write custom tag {%ifowned track request.user%}
>
> > 2. You can try something less elegant:
>
> > {%for user in track.m2m_users.all%}
> > {%ifequal user request.user%}
> > <do what you want here>
> > {%endifequal%}
> > {%endfor%}
>
> > - but it's only good for small apps, as a temp solution.
>
> > --
> > Tomasz Zieliñskihttp://pyconsultant.eu

Chris

unread,
Nov 14, 2009, 8:17:42 AM11/14/09
to Django users
I have got tracks working with the extra() function. I do the
following:

tracks.extra(select={'owned':'SELECT Count(*) FROM shop_track_owners
WHERE track_id=shop_track.id and user_id='+str(user.id)})

This works great for tracks. However, how will I do this when I'm just
passing a queryset of albums or artists to a template? The template
uses

{% for track in album.track_set.all %}

which will get the tracks without the extra owned field added. Is
there a way around this? I don't think adding another function to the
model would do it because it can't get access to the user details so

{% for track in album.track_set_nice.all %}

where Album.track_set_nice adds the extra() bits wont work.

Is there a way of manipulating album in the view so that track_set
doesn't fire off a new query but accesses a pre-gathered set of
tracks? As far as I can tell it doesn't seem to like me adding my own
(non-database) variables to a method, so I don't *think* I can do
something like:

for album in albums:
album.myTempTracks = album.track_set.extra(select={'owned':'SELECT
Count(*) FROM shop_track_owners WHERE track_id=shop_track.id and
user_id='+str(user.id)})

in the view. Does anyone know if there is a way to achieve this? I
tried but it didn't seem to work.

Any other suggestions would be much appreciated.

On Nov 13, 10:19 pm, Matt Schinckel <matt.schinc...@gmail.com> wrote:
> On Nov 14, 1:01 am, Chris <chrisbrett...@googlemail.com> wrote:
>
>
>
> > Thanks for your reply.
>
> > From reading around it sounds as though I am probably not meant to
> > solve this in the template (though this does seem like the easier
> > option).
>
> > I have figured out that I could solve this through an SQL statement as
> > follows:
>
> > select *, (select Count(*) FROM shop_track_owners where
> > track_id=shop_track.id and user_id=<<<insert current user id
> > here>>>)"owned" from shop_track;
>
> > I have run this and it works. There are two problems though:
>
> > 1: How do I do this through django querysets? I would like to keep the
> > database abstracted.
>
> You can execute arbitrary SQL like this using the extra method on aqueryset.  Something like:
Reply all
Reply to author
Forward
0 new messages