I want to implement the followers/following feature in my Django application.
I've an UserProfile class for every User (django.contrib.auth.User):
class UserProfile(models.Model):
user = models.ForeignKey(User, unique = True, related_name = 'user')
follows = models.ManyToManyField("self", related_name = 'follows')
So I tried to do this in python shell:
>>> user_1 = User.objects.get(pk = 1) # <-- mark
>>> user_2 = User.objects.get(pk = 2) # <-- john
>>> user_1.get_profile().follows.add(user_2.get_profile())
>>> user_1.get_profile().follows.all()
[<UserProfile: john>]
>>> user_2.get_profile().follows.all()
[<UserProfile: mark>]
But as you can see, when I add a new user to the follows field of a user, is also added the symmetrical relation on the other side. Literally: if user1 follows user2, also user2 follows user1, and this is wrong.
Where's my mistake? Have you a way for implement followers and following correctly?
Thank you guys.
Set symmetrical to False in your Many2Many relation:
follows = models.ManyToManyField('self', related_name='follows', symmetrical=False)
In addition to mouad's answer, may I suggest choosing a different *related_name*: If Mark follows John, then Mark is one of John's followers, right?
Related
Here I am working with two models.Both models have ForeignKey relation to the User model. Here I wanted to query staffs, is_reviewed_by and sent_by and I tried like this. When I do filter it returns the queryset but when I used related_name to query then it throws AttributeError.
How can i do this?
`Exception Value:
'Leave' object has no attribute 'reviewed_by
models.py
class Leave(models.Model):
staff = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,related_name='staff_leave')
organization = models.ForeignKey(Organization,on_delete=models.CASCADE,related_name='staff_leave')
sub = models.CharField(max_length=300)
msg = models.TextField()
start_day = models.DateField()
end_day = models.DateField()
is_reviewed_by = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,related_name='reviewed_by',blank=True,null=True)
class LeaveReply(models.Model):
staff = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,related_name='leave_status')
leave = models.ForeignKey(Leave,on_delete=models.CASCADE,related_name='leave_status')
sub = models.CharField(max_length=300,blank=True,null=True)
msg = models.TextField(blank=True,null=True)
sent_on = models.DateTimeField(auto_now_add=True)
sent_by = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,related_name='sent_by')
views.py
def leave_detail(request, pk):
leave = get_object_or_404(Leave, pk=pk)
reviewers = leave.reviewed_by.all() # not working
staffs = leave.staff_leave.all() # does not works
staffs = Leave.objects.filter(staff=leave.staff) # this works
reviewers = Leave.objects.filter(is_reviewed_by=leave.is_reviewed_by) # works
reply_sender = LeaveReply.objects.filter(sent_by=leave.is_reviewed_by) #works
reply_sender = leave.sent_by.all() # doesn't works
You're a bit confused. There is nothing to do with related_name here.
You have a Leave object. As the error says, Leave items don't have reviewed_by, sent_by or staff_leave attributes. They have is_reviewed_by and staff; and the only sent_by object is on the LeaveReply object.
Edited answer
models:
class Leave(models.Model):
staff = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,related_name='staff_leave')
organization = models.ForeignKey(Organization,on_delete=models.CASCADE,related_name='staff_leave')
is_reviewed_by = models.ForeignKey(get_user_model(),on_delete=models.CASCADE,related_name='reviewed_by',blank=True,null=True)
I think this model is for Leave details of employee.
Not working code:
reviewers = leave.reviewed_by.all() # not working
staffs = leave.staff_leave.all() # does not works
And this is for, staff who is taking leave and a person who reviewed the leave application. Both fields are related to User model. If we have instance of Leave model (i.e. leave = get_object_or_404(Leave, pk=pk)), then we can get these two by this:
staffs = leave.staff # for person who will be on leave
reviewers = leave.is_reviewed_by # for the person who reviewed the leave
Extra
If we have a User instance (user = get_object_or_404(User, pk=pk)), and we want to know how many leaves he has taken, then we can use related name:
no_of_leaves_itaken = user.staff_leave.all()
or how many leaves he has reviewed:
no_of_leaves_reviewed = user.staff_leave.all()
I have the following Django models:
class Lesson(models.Model):
title = models.TextField()
class Course(models.Model):
lessons = models.ManyToManyField(Lesson)
class User(AbstractUser):
favorites = models.ManyToManyField(Lesson)
I have a route /courses/course_id that returns a course details including an array of lessons (using Django Rest Framework)
How can i return in the lessons object an additional attribute favorite based on my users favorites.
I attempted the following:
course = self.get_object(course_id)
favorites = request.user.favorites
for lesson in course.lessons.all():
if lesson in favorites.all():
lesson.favorite = True
serializer = CourseDetailSerializer(course, context=serializer_context)
return Response(serializer.data)
But when returning it doesn't work:
(django.core.exceptions.ImproperlyConfigured: Field name favorite is
not valid for model Lesson.
My serializers:
class CourseDetailSerializer(serializers.HyperlinkedModelSerializer):
lessons = LessonListSerializer(many=True, read_only=True)
class Meta:
model = Course
fields = ('id', 'lessons', 'name', 'title')
class LessonSerializer(serializers.ModelSerializer):
class Meta:
model = Lesson
fields = ('id', 'title', 'duration', 'favorite')
You cannot add properties to objects if they are not defined, like here:
lesson.favorite = True
When you create m2m relation:
favorites = models.ManyToManyField(Lesson)
... django creates virtual model that simply stores pairs of primary keys from both models. This relation could look like this in database:
id | user_id | lesson_id
------+---------------+----------
151 | 11 | 3225
741 | 21 | 4137
What I think you want to achieve is to add extra information about this relation.
Therefore you need to create intermediary model with that extra field, i.e:
class User(AbstractUser):
favorites = models.ManyToManyField(Lesson, through='UserLessons')
class UserLessons(models.Model):
user = models.ForeignKey(User)
lesson = models.ForeignKey(Lesson)
favorite = models.BooleanField(default=False)
Your lessonmodel doesn't include a favourite boolean, so it isn't able to set it when you call lesson.favorite = True
If you want to get rid of the error, try:
class Lesson(models.Model):
title = models.TextField()
favorite = models.BooleanField(initial=False)
Although it appears that the lessons aren't user-specific. So this solution might not be what you are looking for, because it will set a Lesson's "favourite" field to be true for all users if only one user sets it as a favorite.
I'm trying to do a function that allow a user to follow another one. the problem is when I'm adding a new user to the "followings" the user that follow another user is also added in the following list of the followed user. For example if user a follow user b I will have that:
view.py
def follow_test(request):
name = request.POST.get('name', '')
user_followed = Dater.objects.get(username=name)
current_user = Dater.objects.get(id=request.user.id)
print "Current", current_user.followings.all() # display []
print "Followed", user_followed.followings.all() # display []
current_user.followings.add(user_followed)
print "Current", current_user.followings.all() # display <Dater: b>
print "Followed", user_followed.followings.all() # display <Dater: a>
model.py:
followings = models.ManyToManyField('self', blank=True)
I would like the user b only to be add in the followings of a
By default, many-to-many relationships on self are symmetrical. If you don't want this, set symmetrical to False:
followings = models.ManyToManyField('self', blank=True, symmetrical=False)
See the docs
Just set related_name="followed_by" for the many to many field. That would assign the reverse mapping to followed_by
followings = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='followed_by')
I have a model like this in Django:
class Source_info(models.Model):
user = models.ForeignKey(User)
session_name = models.TextField()
server_uname_path = models.TextField()
server_name = models.TextField()
server_fullpath = models.TextField()
source_username = models.TextField()
make_default = models.TextField()
I get make_default value as:
make_default = request.POST.get('make_default', True)
If user checks on make_default value is 'on' otherwise it's True. One user can have only one make_default i.e. only one 'on' per user. So I want to change previous value to 'True' from on if the user checks on Make default. How can I achieve that?
def your_view(request, id):
#change the current make_default into False
default = SourceInfo.objects.get(user=request.user, make_default=True)
default.make_default = False
default.save()
#the new make_default for user
source = get_object_or_404(SourceInfo, pk=id)
source.make_default = True
source.save()
.................
Here's how I understand your question: each user can have at most one Source_info as their default. So when a user selects this Source_info as their default, you would like to deselect all the other ones for that user.
The reason you are having difficulty with this is that you have the model set up the wrong way round. If each user has at most one default Source_info then you need this to be a foreign key on the user model, not a flag on the Source_info model. So you should write instead:
class User(models.Model):
# ... other fields ...
default_source_info = models.ForeignKey('Source_info', null = True)
(If every user has exactly one default Source_info then you can omit the null = True.)
If the User model in question is the one from django.contrib.auth, then you should see the section "Extending the existing User model" in the Django documentation.
I've got a Form which I'm using the following field in.
contact_country = forms.ModelChoiceField(queryset=Country.objects.all())
The Country model looks like this
class Country(models.Model):
iso = models.CharField(max_length=2)
name = models.CharField(max_length=80)
printable_name = models.CharField(max_length=80)
iso3 = models.CharField(max_length=3,null=True, blank=True)
numcode = models.IntegerField(null=True, blank=True)
special = models.BooleanField(default=False)
def __unicode__(self):
return self.printable_name
class Meta:
ordering = [ 'printable_name' ]
The 'special' field indicates that the country is "special". If the country is "special" I want it to appear before the rest of the listing - as I'm sure you've seen elsewhere on the web (e.g. English speaking countries like Australia, United Kingdom and United States at the top of the select, but also again with the rest of the countries).
Is that possible with QuerySet? Or should I be looking elsewhere?
Does contact_country = forms.ModelChoiceField(queryset=Country.objects.order_by('special')) work?
This is untested, so you may give it a try, but it may not give it what you want...
in your view do this:
specials = Country.objects.filter(special=True)
all_of = Country.objects.all()
# worst thing is, this is a list, not a queryset...
new_list = list(specials)+list(all_of)
# add new object to you form...
YourForm.base_fields['contact_country'] = forms.ChoiceField(choices=[(x.id,x) for x in new_list])
Your handicap is, yo create the list using a list, not directly from queryset
This will solve your pronblem, with a dirty looking way, but it is a solution i used when modelform can not help you...
You can override the default ordering:
class Meta:
ordering = [ '-special', 'printable_name' ]
You can also write a custom manager but it doesn't worth...