Order by count of a ForeignKey field? - django

I have a User model and a Submission model. Each Submission has a ForeignKey field called user_submitted for the User who uploaded it.
class Submission(models.Model):
uploaded_by = models.ForeignKey('User')
class User(models.Model):
name = models.CharField(max_length=250 )
My question is pretty simple: how can I get a list of the three users with the most Submissions?
I tried creating a num_submissions method on the User model:
def num_submissions(self):
num_submissions = Submission.objects.filter(uploaded_by=self).count()
return num_submissions
and then doing:
top_users = User.objects.filter(problem_user=False).order_by('num_submissions')[:3]
But this fails, as do all the other things I've tried. Can I actually do it using a smart database query? Or should I just do something more hacky in the views file?

from django.db.models import Count
top_users = User.objects.filter(problem_user=False) \
.annotate(num_submissions=Count('submission')) \
.order_by('-num_submissions')[:3]
You didn't mention problem_user in your example model code, but I've left it in assuming that it is a BooleanField on User.

Related

Django get all, with related models

Problem:
I'm using Django Rest Framework and i want to fetch all models with the relationships included, like this:
TestModel.objects.all()
My model looks like this:
class TestModel(models.Model):
name = models.CharField(max_length=32)
related_model = models.ForeignKey(TestRelation)
Problem is, i only get the Primary Keys for related_model but i need the whole related_model!
I'm using the ListCreateAPIView, with the above queryset (TestModel.objects.all()) and the most basic form of the ModelSerializer.
I tried the PrimaryKeyRelatedField but i get the same result..
Thanks!
Just create serializer for your related model:
class TestRelationSerializer(serializers.ModelSerializer):
class Meta:
meta = TestRelation
and use is as field in TestModelSerializer:
class TestModelSerializer(serializers.ModelSerializer):
related_model = TestRelationSerializer()
You can also do it other way around, by using TestModelSerializer as field in TestRelationSerializer with many set to true:
class TestRelationSerializer(serializers.ModelSerializer):
testmodel_set = TestModelSerializer(many=True)
just remember, you can't do both at once due to infinite recursion it makes.

Django ORM access User table through multiple models

views.py
I'm creating a queryset that I want to serialize and return as JSON. The queryset looks like this:
all_objects = Program.objects.all()
test_data = serializers.serialize("json", all_objects, use_natural_keys=True)
This pulls back everything except for the 'User' model (which is linked across two models).
models.py
from django.db import models
from django.contrib.auth.models import User
class Time(models.Model):
user = models.ForeignKey(User)
...
class CostCode(models.Model):
program_name = models.TextField()
...
class Program(models.Model):
time = models.ForeignKey(Time)
program_select = models.ForeignKey(CostCode)
...
Question
My returned data has Time, Program, and CostCode information, but I'm unable to query back the 'User' table. How can I get back say the 'username' (from User Table) in the same queryset?
Note: I've changed my queryset to all_objects = Time.objects.all() and this gets User info, but then it doesn't pull in 'CostCode'. My models also have ModelManagers that return the get_by_natural_key so the relevant fields appear in my JSON.
Ultimately, I want data from all four models to appear in my serialized JSON fields, I'm just missing 'username'.
Here's a picture of how the JSON object currently appears in Firebug:
Thanks for any help!
It seems a bit heavyweight at first glance but you could look at using Django REST Framework:
http://www.django-rest-framework.org/api-guide/serializers#modelserializer
You can define and use the serializer classes without having to do anything else with the framework. The serializer returns a python dict which can then be easily dumped to JSON.
To get all fields from each related model as nested dicts you could do:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
depth = 2
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
To customise which fields are included for each model you will need to define a ModelSerializer class for each of your models, for example to output only the username for the time.user:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', )
class TimeSerializer(serializers.ModelSerializer):
"""
specifying the field here rather than relying on `depth` to automatically
render nested relations allows us to specify a custom serializer class
"""
user = UserSerializer()
class Meta:
model = Time
class ProgramSerializer(serializers.ModelSerializer):
time = TimeSerializer()
class Meta:
model = Program
depth = 1 # render nested CostCode with default output
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
What you really want is a "deep" serialization of objects which Django does not natively support. This is a common problem, and it is discussed in detail here: Serializing Foreign Key objects in Django. See that question for some alternatives.
Normally Django expects you to serialize the Time, CostCode, Program, and User objects separately (i.e. a separate JSON array for each) and to refer to them by IDs. The IDs can either be the numeric primary keys (PKs) or a "natural" key defined with natural_key.
You could use natural_key to return any fields you want, including user.username. Alternatively, you could define a custom serializer output whatever you want there. Either of these approaches will probably make it impossible to load the data back into a Django database, which may not be a problem for you.

django: how do I query based on GenericForeignKey's fields?

I'm new in using GenericForeignKey, and I couldn't make it to work in a query statement. The tables are roughly like the following:
class Ticket(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
class Issue(models.Model):
scan = models.ForeignKey(Scan)
A scan creates one issue, an issue generates some tickets, and I made Issue as a foreign key to Ticket table. Now I have a Scan object, and I want to query for all the tickets that related to this scan. I tried this first:
tickets = Tickets.objects.filter(issue__scan=scan_obj)
which doesn't work. Then I tried this:
issue = Issue.objects.get(scan=scan_obj)
content_type = ContentType.objects.get_for_model(Issue)
tickets = Tickets.objects.filter(content_type=content_type, issue=issue)
Still doesn't work. I need to know how to do these kind of queries in django? Thanks.
The Ticket.issue field you've defined will help you go from a Ticket instance to the Issue it's attached to, but it won't let you go backwards. You're close with your second example, but you need to use the issue_id field - you can't query on the GenericForeignKey (it just helps you retrieve the object when you have a Ticket instance). Try this:
from django.contrib.contenttypes.models import ContentType
issue = Issue.objects.get(scan=scan_obj)
tickets = Ticket.objects.filter(
issue_id=issue.id,
issue_ct=ContentType.objects.get_for_model(issue).id
)
Filtering across a GenericForeignKey can by creating a second model that shares the db_table with Ticket. First split up Ticket into an abstract model and concrete model.
class TicketBase(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
class Meta:
abstract = True
class Ticket(TicketBase):
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
Then create a model that also subclasses TicketBase. This subclass will have all the same fields except issue which is instead defined as a ForeignKey. Adding a custom Manager allows it to be filtered to just a single ContentType.
Since this subclass does not need to be synced or migrated it can be created dynamically using type().
def subclass_for_content_type(content_type):
class Meta:
db_table = Ticket._meta.db_table
class Manager(models.Manager):
""" constrain queries to a single content type """
def get_query_set(self):
return super(Manager, self).get_query_set().filter(issue_ct=content_type)
attrs = {
'related_to': models.ForeignKey(content_type.model_class()),
'__module__': 'myapp.models',
'Meta': Meta,
'objects': Manager()
}
return type("Ticket_%s" % content_type.name, (TicketBase,), attrs)

django query set values() specific fields to include user profile

I have 2 django models like this:
class UserProfile(models.Model):
user = models.OneToOneField(User)
organisation = models.CharField(max_length=200)
class Submission(models.Model):
user = models.ForeignKey(User)
date_submission = models.DateTimeField(db_index=True, default=datetime.now())
date_published = models.DateTimeField(db_index=True, null=True)
status = models.CharField(db_index=True, max_length=4000)
logfile = models.TextField(db_index=False)
where each 'Submission' object is owned by a normal django user, and each user has a UserProfile configured using the AUTH_PROFILE_MODULE in the normal way.
This works as you would expect, i can see the organisation field of the UserProfile object:
Submission.objects.all()[0].user.userprofile.organisation
When i want to serialize the Submissions list (for export to json) normally i use:
Submission.objects.all().values()
# or for more control of fields
Submission.objects.all().values('status', 'date_submission')
# to traverse a relationship.. get info from the User object
Submission.objects.all().values('user__username')
.. these work fine. BUT my problem is that i cannot:
Submission.objects.all().values('user__userprofile__organisation')
raise FieldError("Invalid field name: '%s'" % name)
django.core.exceptions.FieldError: Invalid field name: 'user__userprofile__organisation'
so it seems that the UserProfile is a 'special case'. This was discussed here:
Django query : Call values() on user__userprofile
but the solution doesn't help me (i'm fairly sure..)
Is this a limitation of the values() method ? does anyone know a way to get the same output of values() but able to traverse the UserProfile model ?
thanks for any ideas
-i
Turns out upgrading to version 1.4 of Django solved the problem, the comment by Jingo suggests 1.3.1 would also be ok.
so now i can:
query_set = Submission.objects.filter()
query_set.values('user__userprofile__organisation')
[{'user__userprofile__organisation': u'organisation test 1'}]
cheers
-i

django - how do I join the results of multiple models having foreign key the same user?

I have multiple models that point to the same user like this:
class Model1(moldels.Model):
user = models.ForeignKey(User)
title = models.CharField()
...
class Model2(moldels.Model):
user = models.ForeignKey(User)
...
class Model3(moldels.Model):
user = models.ForeignKey(User)
...
What to be able to do a search/filter on title field on Model1 and join the results from the other models that have the same user. Only Moldel1 will return multiple results for the same user. Model2 and Model3 will always return one result for every user (like one avatar and one profile). - Thank you!
It depends on what you mean by "join". You can access one from any other though user and the related_name of the model (by default: <lowercase class name>_set), e.g.:
model1_instance.user.model2_set.all()
Or you can use the user instance to access each directly:
user.model1_set.all()
user.model2_set.all()
user.model3_set.all()
You can query those models through the user as well, with query traversals:
User.objects.filter(model1__title='some title')
Finally, for the models that have only one instance, such as one profile per user, you should really be using OneToOneField instead of ForeignKey. If you do that, then you can use select_related on any reverse relation that is a OneToOneField (true SQL JOIN), e.g.:
User.objects.filter(model1__title='some title').select_related('model2', 'model3')
However, select_related will not work on reverse foreign key relationship or many-to-many relationships. For that, you'll have to wait until Django 1.4 drops with prefetch_related.
If you want get a queryset, this is impossible. You cannot get a queryset of various models.
But I think that you want something like this:
objs1 = Model.objects.filter(title__icontains='YOUR FILTER')
users = objs1.values('user').distinct()
objs2 = Model2.objects.filter(user__in=users)
objs3 = Model3.objects.filter(user__in=users)
objs = list(objs1)
objs.extend(list(objs2))
objs.extend(list(objs3))
return objs