I have a Project model similar to:
class Project(models.Model):
...
lead_analyst=models.ForeignKey(User, related_name='lead_analyst')
...
I want to use django aggregation to return all users with a count of projects per user. Something like:
models.User.objects.annotate(project_count=Count('project_set'))
Only that doesn't work because the auth.user has no knowledge about my Project class. I get the error:
Cannot resolve keyword 'project_set' into field. Choices are: date_joined, email, employee, first_name, groups, id, is_active, is_staff, is_superuser, last_login, last_name, lead_analyst, logentry, message, password, siteprofile, submitter, user_permissions, username
Two part question, really:
How to obtain this count of Projects per auth.user
Is there a better way to write the association between my Project class and the auth.user class that would make this aggregation viable?
Or should I just break into raw sql for aggregations (either via raw sql in django or a view in the database)?
Nothing wrong with your models - that's exactly how to set up that relation. The problem is simply that you've specified a related_name in the foreignkey, so as far as User is concerned, there's no project_set - there's a lead_analyst instead. In fact you can see this in the list of fields given by your error message.
So your aggregate query should read:
models.User.objects.annotate(project_count=Count('lead_analyst'))
(I must say, I think you've chosen a misleading related name here - you would be better off sticking to the default, in which case your original syntax would have worked.)
Related
Disclaimer: I'm new to Django, so I'm still learning the Django way of doing things.
The project I'm working on has a uUser model and a Student model. Previously, the UserAdmin was doing double duty for both models, but now the time has come to create an independent StudentAdmin that allows admins to easily edit/create/etc students.
User contains base info for Student (i.e. first and last name, email, phone, etc.) and Student contains more info (i.e. parent phone, class, todos, etc.). Related models like 'Class', 'Grade', etc. have FK relationships to User, which at first didn't strike me as an issue.
But when I went to reuse the inline classes created for UserAdmin in the new StudentAdmin, I've run into this error: <class 'my_project.admin.TodoInline'>: (admin.E202) 'my_project.Todo' has no ForeignKey to 'my_project.Student. This isn't surprising, so I thought I could merely override the _init_ method on TodoInline to use parent_model.user, but then I ran into errors relating to ForwardOneToOneDescriptor and missing _meta fields. Given this use case, I believe there has to be a solution to this, but I'm currently at a loss for Django-related vocabulary for researching this issue further.
class TodoInline(admin.TabularInline):
model = Todo
fields = ['content', 'completed', 'category', 'due_date']
verbose_name_plural = "To-do's"
extra = 0
# This doesn't work:
def __int__(self, parent_model, admin_site):
super(TodoInline, self).__init__(parent_model.user, admin_site)
If the answer to this issue is to redefine the FK relationships between models, that's something I can't do at the moment. I need a solution that will allow me to reorient the inline classes (TodoInline is just one of many that I need for StudentAdmin) to use the related User model on the Student model. Thank you in advance!
Despite of your restriction not to reconfigure your foreign key, I see only one possible solution with two different modellings here.
But first of all: If you get the error, a foreign key to your model student is required, why can't you reconfigure that foreign key?
Second: You want to access the parentmodel of student and you've got an foreign key to the model user. From a modelling point of view I wonder, how that is useful - as you want to operate with a student, not with a user? Additionally, if your studentis related to user you can access all userattributes through student. So there is really no point in avoiding the reconfiguration of your foreign key.
So the only two possible (and meaningful) modellings I see are:
(1) Let the model student inherit from user, so student has all field's and methods which are implemented to user. Then reconfigure the foreign key to student (which requires $ python manage.py makemigrations <app> and $ python manage.py migrate <app>)
or
(2)
Define a one-to-one relation between student and user, so a student is uniquely to a user. This also requires to reconfigure your foreign key as describbed in (1).
If student does not differ from user there is an (3) option: Proxy models. With proxy models you can implement multiple modelAdmin for one model.
In a Django app of mine, I need to display, for a list of users (called user_ids), their:
username, avatar and score.
The username is retrieved from the User model, whereas avatar and score are present in the UserProfile model (that has a one-to-one field point to the User model, called user).
Currently my approach is to fetch the full objects (see below), even though I just need 3 attributes from the two models.
What's the most efficient way for me to just retrieve just the required fields, nothing else? Now I know i can do:
usernames = User.objects.filter(id__in=user_ids).values_list('username',flat=True)
scores_and_avatars = UserProfile.objects.filter(user_id__in=user_ids).values_list('score','avatar')
However, these give me separate querysets, so I can't iterate over them as a unified object_list and show each user's username, score and avatar in a Django template. So what would be the most efficient, light-weight way to retrieve and put this information together?
Currently, I'm doing the following to retrieve these three fields: queryset = User.objects.select_related('userprofile').filter(id__in=user_ids)
The most efficient way it's use values, your query will look like this:
usernames = User.objects.filter(id__in=user_ids).values('username', 'userprofile__score', 'userprofile__avatar')
This solution will return dictionary, but you can try to use only instead of values, it'll create django model object with specified fields, but if you will try to access not specified field it'll generate additional query.
usernames = User.objects.filter(id__in=user_ids).select_related('userprofile').only('username', 'userprofile__score', 'userprofile__avatar')
In Django, I want to filter a QuerySet using a list of Users who the active user is following.
I've opted to extend the User class rather than replace it with a custom class, although I'm not sure that was the right choice.
Hence what I have is a UserProfile class, which has a ManyToManyField to other UserProfiles, and a OneToOneField with User.
My QuerySet looks like Entry.objects.filter(author__in=request.user.userprofile.following.all()) but author is a ForeignKeyField to User rather than UserProfile, so I'm about to change Entry.author to point to UserProfiles instead.
So my questions are, in decreasing priority:
Is it right to have author be a UserProfile instead? Because then I have something like entry.author.user.username which is not intuitive.
Might it be better to just replace the builtin User class with a custom class which has the data I need?
Is it right for UserProfile's following to be a ManyToManyField to other UserProfile rather than to User?
I don't recommend this at all. As you said it's not intuitive, an
author should be a user.
No, a one to one relationship to user is better than creating a custom user class.
I think it will look better if you connect followings to users. This way your original query will also work.
In UserProfile model:
following = models.ManyToManyField(User, related_name="followed_by")
In this scenario user.followed_by is a list of UserProfiles, but user.userprofile.following is a list of users.
Since your users can follow each other and Entries are from those people, it makes totally sense to make the author UserProfile so that both models are logically in the same level
How to store field names which user can see.
What is the best/correct way to save rights/access to fields in table.
I want store in UserModel which fields allowed to this user, from DataModel
E.G: user is allowed to see name, commision and id, another user is allowed to see name, seller, price and custom_data_field7
I would recommend django permissions system, or create something simpler, that will decide what fields should be shown based on user type. Depending on where would you like it to show (is it in template or REST API), I could come up with some ideas.
Two questions please:
I need two foreign keys back to User, one for author and one for coauthor. I have managed by setting related_name equal to + but am not sure if this is smart or the best way. Thoughts or pointers?
When making an add entry via the django admin for this model, the author choices are names like john_smith, etc. Where would I call get_full_names() from in order to display full names rather than usernames with underscores? Thanks.
from django.contrib.auth.models import User
from django.db import models
class Books(models.Model):
title = models.CharField()
author = models.ForeignKey(User)
coauthor = models.ForeignKey(User, related_name='+')
Kevin
I would change the related name to a value that is more intelligible - such as books_where_coauthor and also add a similar one of books_where_author as then you can get the relevant books by going from theuser.books_where_author.all() etc
Regarding your Admin query, you're getting the username because that's what the default __unicode__() method of User spits out.
Unless you'd like to hack your contrib.auth.models file (not recommended), I'd suggest using a custom modelform in the admin, and manually setting the names of the choices in the ModelChoiceField, either by subclassing that field and making a custom one that renders its widget with get_full_name if possible, or do it via something like this snippet. That said, I am sure there's a simpler way to do that, but I've forgotten. Dangit.
With regard to the second part of my question (displaying full names instead of usernames in the ChoiceField of a form), I found this link to be just the ticket: