I am creating an API using the drf-nested-routers application for Django Rest Framework. This application is a tracker where users have sessions and tasks. Each user can have three active tasks and can work on each of these tasks in a given session.
My (abbreviated) models are:
#models.py
class User(models.Model):
name = models.Charfield()
class Task(models.Model):
start_date = models.Datefield()
task_title = models.Charfield()
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Session(models.Model):
session_date = models.Datefield()
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sessions')
task_one = models.ForeignKey(related_name="task_one")
task_one_attempts = models.IntegerField()
task_two = models.ForeignKey(related_name="task_two")
task_two_attempts = models.IntegerField()
I have created the following (abbreviated) Serializers for these models:
#serializers.py
class TaskSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(many=False)
class Meta:
model = Task
fields = ('start_date', 'task_title', 'user')
class SessionSerializer(serializers.ModelSerializer):
user = Serializers.StringRelatedField(many=False)
class Meta:
model = Session
fields = ('session_date', 'user', 'task_one', 'task_one_attempts', 'task_two', 'task_two_attempts')
class UserSerializer(models.ModelSerializer):
sessions = SessionSerializer(many=True)
tasks = TaskSerializer(many=True)
sessions = SessionSerializer(many=True)
class Meta:
model = Users
fields = ('name', 'sessions', 'tasks')
I also have my views.py and urls.py set up to do the routing properly.
I can navigate to the sessions and tasks API views just fine. However, whenever I try to navigate to the user view, it throws the following error:
'User' object has no attribute 'tasks'.
What's really interesting, though, is that if I remove 'tasks' and just include sessions, it serializes everything just fine and gives me a nested view of the User's various sessions.
I'm at a loss here and would appreciate any assistance.
I rubber-ducked it with my wife and figured out my problem.
I had 'related_name="sessions"' in my ForeignKey field for user in models.py.
I was missing that information in the ForeignKey field in the task model.
Hopefully someone else stumbles on this and can learn from my mistake.
Related
I am having 3 different models - User, Thread and UserProfile.
User model contains information like ID, First_name and Last_name.
Thread model contains information like
class Thread(models.Model):
first_person = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, related_name='thread_first_person')
second_person = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True,related_name='thread_second_person')
updated = models.DateTimeField(auto_now=True)
and UserProfile model,
class UserProfile(models.Model):
custom_user = models.OneToOneField(CustomUser, on_delete=models.CASCADE)
picture = models.ImageField(default='profile_image/pro.png', upload_to='profile_image', blank=True)
when I am trying to get all the threads from Thread model and pass it from views.py to my HTML template then I can access User model fields like -
{{ thread.second_person.ID}} {{ thread.second_person.First_name}}
But how can I access picture field from UserProfile with the help of custom_user ?
I'm a little confused: you say you have three models User, Thread and UserProfile. But inside your UserProfile model you reference a CustomUser model as the one-to-one relationship for the custom_user field. Then in your Thread model you reference a plain User model as the FKs for the first_person and second_person fields. Do you have 3 models or 4?
Assuming you only have 3 models, and that CustomUser is actually just User, then what you're trying to achieve should be doable. However you may need to change your conventions regarding related names to best practices in order to do so cleanly.
I have set up the models I think you need roughly below, and the code needed to access the relevant parts of each model within the template layer:
#models.py
class User(AbstractBaseUser):
# User Model Code
class Thread(models.Model):
first_person = models.ForeignKey(
User,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='thread_first_persons' # Note the Plural
)
# Related name for a FK is a one-to-many relationship
# (i.e. 1 User can be first_person on many threads)
# This may not be your desired behaviour, but it is possible on current set-up
second_person = models.ForeignKey(
User,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='thread_second_persons'
) # As above
updated = models.DateTimeField(auto_now=True)
class UserProfile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE
related_name='user_profile'
)
# Note: custom_user is confusing nomenclature.
# The above is best practice for User <> UserProfile 1-to-1 relationships
picture = models.ImageField(
default='profile_image/pro.png',
upload_to='profile_image',
blank=True
)
Now when you want access to the UserProfile from the Thread object, you can do so as follows inside a template:
{{ thread.second_person.user_profile.picture }}
Side Note: in your views.py file, if you are sending just the thread to your template, then to save your database several queries I would optimise with the following select_related parameters:
#views.py
threads = Thread.objects.select_related(
'first_person', 'first_person__user_profile',
'second_person', 'second_person__user_profile'
).all()
thread = Thread.objects.select_related(
'first_person', 'first_person__user_profile',
'second_person', 'second_person__user_profile'
).get(id=id)
How can i alter my code so that the user logged is not able to follow themselves. I tried unique_together but could not get it to work
I will be using a button on other users profile pages to add the user to the logged in users following list in this table.
class FollowList(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
following = models.ManyToManyField(User, related_name='followers')
I'd suggest you use something like intermediary model. ManyToMany in fact is a model with two ForeignKey fields: first is for your FollowList model and second is for linking the User instance. So you have an extra relation to your model. Better way:
class Follow(models.Model):
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='my_following_users')
following = models.ForeignKey(User, on_delete=models.CASCADE, related_name='my_followers')
class Meta:
unique_together = (
('follower', 'following'),
)
def save(self, *args, **kwargs):
if self.follower.pk != self.following.pk: # preventing of following themselves
return super().save(*args, **kwargs)
# use like this
dev YourView():
Follow.objects.create(follower=request.user, following=user)
UPD:
If you need to set multiple followers with once request, do something like this:
dev YourView():
items = []
for uid in ("<user ids here>"):
items.append(Follow(follower=request.user, following__pk=uid))
Follow.objects.bulk_create(items)
I am new to Django and trying to create an App with two User Types (Freelancers and Customers). I understand how to create a User profile Class and it works well for me:
class UserProfile(models.Model):
user = models.OneToOneField(User)
description = models.CharField(max_length=100, default='')
country = models.CharField(max_length=100, default='')
website = models.URLField(default='')
phone = models.IntegerField(default=0)
def create_profile(sender, **kwargs):
if kwargs['created']:
user_profile = UserProfile.objects.create(user=kwargs['instance'])
post_save.connect(create_profile, sender=User)
This works well for me on a one user type user. But now I am building an app with 2 types of users (freelancers and customers), what is the best approach to get this done. Both users will have different view and info. Should I:
Create 2 different apps, and repeat the normal registeration and login for each.
If I do the above, hope the freelancers when logged in won't access customers view.
How do I add user type to the user profile if I decide to use one app and model for it.
Please I need a step by step beginner approach, or a link to relevant source.
Thanks.
You could try this:
class UserProfile(models.Model):
user = models.ForeignKey(User)
#define general fields
class Freelancer(models.Model):
profile = models.ForeignKey(UserProfile)
#freelancer specific fields
class Meta:
db_table = 'freelancer'
class Customers(models.Model):
profile = models.ForeignKey(UserProfile)
#customer specific fields
class Meta:
db_table = 'customer'
You can then have as many Users as you want from the UserProfile.
You should need just use Groups Django mechanism - you need to create two groups freelancer and let say common and check whether user is in first or second group - then show him appropriate view
To check whether user is in group you can use
User.objects.filter(pk=userId, groups__name='freelancer').exists()
You Could Try extending the Default Django Auth User like this
Create an App with Account or Whatever name you like , then in models.py write like below
class User(AbstractUser):
is_head = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
is_public = models.BooleanField(default=False)
Add Auth Extended Model in Settings.py
AUTH_USER_MODEL = 'accounts.User'
Migrate your Account app and you are all set with Your User Extended Model.
I have a Profiles app that has a model called profile, i use that model to extend the django built in user model without subclassing it.
models.py
class BaseProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='owner',primary_key=True)
supervisor = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='supervisor', null=True, blank=True)
#python_2_unicode_compatible
class Profile(BaseProfile):
def __str__(self):
return "{}'s profile". format(self.user)
admin.py
class UserProfileInline(admin.StackedInline):
model = Profile
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline ]
admin.site.unregister(User)
admin.site.register(User, NewUserAdmin)
admin
the error is
<class 'profiles.admin.UserProfileInline'>: (admin.E202) 'profiles.Profile' has more than one ForeignKey to 'authtools.User'.
obviously i want to select a user to be a supervisor to another user. I think the relationship in the model is OK, the one that's complaining is admins.py file. Any idea ?
You need to use multiple inline admin.
When you have a model with multiple ForeignKeys to the same parent model, you'll need specify the fk_name attribute in your inline admin:
class UserProfileInline(admin.StackedInline):
model = Profile
fk_name = "user"
class SupervisorProfileInline(admin.StackedInline):
model = Profile
fk_name = "supervisor"
class NewUserAdmin(NamedUserAdmin):
inlines = [UserProfileInline, SupervisorProfileInline]
Django has some documentation on dealing with this: https://docs.djangoproject.com/en/1.9/ref/contrib/admin/#working-with-a-model-with-two-or-more-foreign-keys-to-the-same-parent-model
Here is an example that I have just tested to be working
class Task(models.Model):
owner = models.ForeignKey(User, related_name='task_owner')
assignee = models.ForeignKey(User, related_name='task_assigned_to')
In admin.py
class TaskInLine(admin.TabularInLine):
model = User
#admin.register(Task)
class MyModelAdmin(admin.ModelAdmin):
list_display = ['owner', 'assignee']
inlines = [TaskInLine]
I have an application that makes use of Django's UserProfile to extend the built-in Django User model. Looks a bit like:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
# Local Stuff
image_url_s = models.CharField(max_length=128, blank=True)
image_url_m = models.CharField(max_length=128, blank=True)
# Admin
class Admin: pass
I have added a new class to my model:
class Team(models.Model):
name = models.CharField(max_length=128)
manager = models.ForeignKey(User, related_name='manager')
members = models.ManyToManyField(User, blank=True)
And it is registered into the Admin:
class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'manager')
admin.site.register(Team, TeamAdmin)
Alas, in the admin inteface, when I go to select a manager from the drop-down box, or set team members via the multi-select field, they are ordered by the User numeric ID. For the life of me, I can not figure out how to get these sorted.
I have a similar class with:
class Meta:
ordering = ['name']
That works great! But I don't "own" the User class, and when I try this trick in UserAdmin:
class Meta:
ordering = ['username']
I get:
django.core.management.base.CommandError: One or more models did not validate:
events.userprofile: "ordering" refers to "username", a field that doesn't exist.
user.username doesn't work either. I could specify, like image_url_s if I wanted to . . . how can I tell the admin to sort my lists of users by username? Thanks!
This
class Meta:
ordering = ['username']
should be
ordering = ['user__username']
if it's in your UserProfile admin class. That'll stop the exception, but I don't think it helps you.
Ordering the User model as you describe is quite tricky, but see http://code.djangoproject.com/ticket/6089#comment:8 for a solution.
One way would be to define a custom form to use for your Team model in the admin, and override the manager field to use a queryset with the correct ordering:
from django import forms
class TeamForm(forms.ModelForm):
manager = forms.ModelChoiceField(queryset=User.objects.order_by('username'))
class Meta:
model = Team
class TeamAdmin(admin.ModelAdmin):
list_display = ('name', 'manager')
form = TeamForm
This might be dangerous for some reason, but this can be done in one line in your project's models.py file:
User._meta.ordering=["username"]
For me, the only working solution was to use Proxy Model. As stated in the documentation, you can create own proxy models for even built-in models and customize anything like in regular models:
class OrderedUser(User):
class Meta:
proxy = True
ordering = ["username"]
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
After that, in your model just change Foreign Key to:
user = models.OneToOneField(OrderedUser, unique=True)
or even more suitable
user = models.OneToOneField(OrderedUser, unique = True, parent_link = True)