Many to many field django add the relationship both way - python

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')

Related

Loop through and Merge Querysets

I am sure this one is straight forward but I cannot seem to get my head around it.
I have "Users" who can post "Posts" on my site.
Each user can follow other users.
The idea is to display all the posts posted by the users that current user is following.
Example : Foo followed Bar and Baz. I need to retrieve all the posts from Bar and Baz.
Bar = Post.objects.filter(user=3)
Baz = Post.objects.filter(user=4)
totalpost= list(chain(Bar, Baz))
print(totalpost)
On this occasion, when both variables userXposts and temp are hardcoded, I can easily retrieve ONE list of QuerySets neeatly by chaining both QuerySets.
However, I cannot have those hardcoded. As such, I am attempted to loop through each user posts and add it in a list since my user can follow X amount of users :
QuerySet = Profile.objects.filter(follower=1)
for x in QuerySet:
userXposts = Post.objects.filter(user=x.user.id)
temp = userXposts
totalpost= list(chain(userXposts, temp))
temp = []
print("Totalpost after union of userpost and temp: ", totalpost)
Here, Profile.objects.filter(follower=1) return two sets of QuerySets, one for Baz and one for Bar.
The problem that I have so far is that totalpost endup being a "list of list" (I believe) which forces me to call totalpost[0] for Bar posts and totalpost[1] for Baz posts.
Since I am attempting to use Pagination with Django, I am forced to pass ONE Variable only in p= Paginator(totalpost, 200)
Would you be able to assist in the loop so that I can fetch the data for the first user, add it to a variable, then go to the second user and ADD the second QuerySet data to the list where the First User data is?
Thanks a lot !
EDIT :
Here are the Models :
class User(AbstractUser):
pass
class Profile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
following = models.ManyToManyField(User, blank=True, related_name="following_name")
follower = models.ManyToManyField(User, blank=True, related_name="follower_name")
def __str__(self):
return f'"{self.user.username}" is followed by {self.follower.all()} and follows {self.following.all()}'
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
timestamp = models.DateTimeField(default=datetime.now)
post = models.CharField(max_length=350, null=True, blank=True)
like = models.ManyToManyField(User, blank=True, related_name="like_amount")
def __str__(self):
return f'#{self.id}: "{self.user.username}" posted "{self.post}" on "{self.timestamp}". Like : "{self.like.all()}" '
Post.objects.filter(user__ following_name__id=1)

Dynamic field value in Django class

I want to create one dynamic field value for my class in Django using PyCharm.
CATEGORY_CHOICES = (
('on','one'),
('tw','two'),
('th','three'),
('fo','four'),
('fi','five'),
)
class art(models.Model):
Title=models.CharField(max_length=300)
Desciption=models.TextField()
Category=models.CharField(max_length=2, choices=CATEGORY_CHOICES)
I want the category field in my class to take more than one option, maybe two or more.
Any help would be appreciated.
If you want one python model to have multiple categories, then you need django ManyToManyField. Basically one model object could have multiple choices, one choice can also belong to multiple models objects:
class Category(models.Model):
category_name = models.CharField(max_length=10, unique=True)
class Art(models.Model):
title = models.CharField(max_length=300)
description = models.TextField()
category = models.ManyToManyField('Category', blank=True)
Note that I put unique=True for category_name to avoid creating duplicate categories.
Something not related, you shouldn't use lower fist in model name, and upper first for field name, that's really BAD naming convention and might confuse others who read your code.
Example:
# create your category in code or admin
one = Category.objects.create(category_name='one')
two = Category.objects.create(category_name='two')
three = Category.objects.create(category_name='three')
# create a new art obj
new_art = Art.objects.create(title='foo', description='bar')
# add category to Art obj
new_art.category.add(one)
new_art.category.add(two)
# category for new art obj
new_art_category = new_art.category.all()
# get only a list of category names
category_names = new_art_category.values_list('category_name', flat=True)
# create another Art obj
new_art2 = Art.objects.create(title="test", description="test")
# assign category to new_art2
new_art2.category.add(two)
new_art2.category.add(three)
Django doc for many to many and python pep8 doc.

How can I detect if a user tries to upvote once again?

I'm currently in the process of implementing an upvoting system ( no down voting system will be used in the app). I managed to create an upvote property to the Post model in my app. The default for that property is 0 as shown here:
models.py
class User(UserMixin, Model):
username = CharField(unique= True)
email = CharField(unique= True)
password = CharField(max_length = 100)
joined_at = DateTimeField(default = datetime.datetime.now)
is_admin = BooleanField(default = False)
confirmed = BooleanField(default = False)
confirmed_on = DateTimeField(null=True)
class Meta:
database = DATABASE
order_by = ('-joined_at',)
def get_posts(self):
return Post.select().where(Post.user == self)
def get_stream(self):
return Post.select().where(
(Post.user == self)
)
#classmethod
def create_user(cls, username, email, password, is_admin= False, confirmed = False, confirmed_on = None):
try:
with DATABASE.transaction():
cls.create(
username = username,
email = email,
password = generate_password_hash(password),
is_admin = is_admin,
confirmed = confirmed,
confirmed_on = confirmed_on)
except IntegrityError:
raise ValueError("User already exists")
class Post(Model):
timestamp = DateTimeField(default=datetime.datetime.now)
user = ForeignKeyField(
rel_model = User,
related_name = 'posts'
)
name = TextField()
content = TextField()
upvotes = IntegerField(default=0)
url = TextField()
category = TextField()
class Meta:
database = DATABASE
order_by = ('-timestamp',)
I managed to increment the value by making the user follow a link:
stream.html
<div class="voting_bar">
<img src="/static/img/upvote.png">
<p>{{post.upvotes}}</p>
</div>
This will activate a function with the associated route:
app.py
#app.route('/vote/<int:post_id>')
def upvote(post_id):
posts = models.Post.select().where(models.Post.id == post_id)
if posts.count() == 0:
abort(404)
post = models.Post.select().where(models.Post.id == post_id).get()
query = models.Post.update(upvotes = (post.upvotes+1)).where(models.Post.id == post_id)
query.execute()
return redirect(url_for('index'))
My question is, how can I detect if the user had already voted? I'm not sure what I should do to accomplish this. My plan was that if I identify if the user tried to upvote again, what I would do is simply decrement their previous vote.
I think the best approach here would be to create a separate table called Votes which will have two columns. One will store the id of the Post and the other will store the id of the User. Each entry or row inside the table would count as one vote. If a user tries to vote on a particular post, you would first query the Votes table if a row with that user id exists. If it doesn't exist, you add the vote. If it does exist however, then you simply remove the vote. To get the total vote count of a particular post, you would again query the Votes table and count the number of rows with the given post id. This would also make your application scalable if in case you would like to add a downvote functionality in the future.
Building on Bidhan's answer, you could implement something like this:
class Upvote(Model):
user = ForeignKeyField(User)
post = ForeignKeyField(Post)
class Meta:
indexes = (
(('user', 'post'), True), # Unique index on user+post
)
You could add methods to post:
def add_vote(self, user):
try:
with DATABASE.atomic():
Vote.create(user=user, post=self)
except IntegrityError:
return False # User already voted
else:
return True # Vote added
def num_votes(self):
return Vote.select().where(Vote.post == self).count()
Also just a tip, but you might use atomic instead of transaction, since the former supports nesting.
You need to maintain a (de-duped) list of users who upvoted, on that post itself.
Maybe you could store a variable on the profile of the user whom upvoted the comment that signified that they had upvoted the comment previously?
Then check if the upvoter had upvoted previously and prevent the tally from increasing while still keeping the tally.
edit
Or you could create a list to track the people who had upvoted like so:
class Post(Model):
timestamp = DateTimeField(default=datetime.datetime.now)
user = ForeignKeyField(
rel_model = User,
related_name = 'posts'
)
name = TextField()
content = TextField()
upvotes = IntegerField(default=0)
upvoters = []
url = TextField()
category = TextField()
Append the users who upvoted to post.upvoters and then check the list before upping the tally
You make an account for each User in the Django server and Authenticate them using Django's inbuilt authorization module.
https://docs.djangoproject.com/en/1.8/topics/auth/
[ To check for a different version change the 1.8 in the url to the version you want ]
Using this you can ensure that only 1 user makes an upvote by using a Flag. When the upvote is done, the flag is set.

How to implement followers/following in Django

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?

A QuerySet by aggregate field value

Let's say I have the following model:
class Contest:
title = models.CharField( max_length = 200 )
description = models.TextField()
class Image:
title = models.CharField( max_length = 200 )
description = models.TextField()
contest = models.ForeignKey( Contest )
user = models.ForeignKey( User )
def score( self ):
return self.vote_set.all().aggregate( models.Sum( 'value' ) )[ 'value__sum' ]
class Vote:
value = models.SmallIntegerField()
user = models.ForeignKey( User )
image = models.ForeignKey( Image )
The users of a site can contribute their images to several contests. Then other users can vote them up or down.
Everything works fine, but now I want to display a page on which users can see all contributions to a certain contest. The images shall be ordered by their score.
Therefore I have tried the following:
Contest.objects.get( pk = id ).image_set.order_by( 'score' )
As I feared it doesn't work since 'score' is no database field that could be used in queries.
Oh, of course I forget about new aggregation support in Django and its annotate functionality.
So query may look like this:
Contest.objects.get(pk=id).image_set.annotate(score=Sum('vote__value')).order_by( 'score' )
You can write your own sort in Python very simply.
def getScore( anObject ):
return anObject.score()
objects= list(Contest.objects.get( pk = id ).image_set)
objects.sort( key=getScore )
This works nicely because we sorted the list, which we're going to provide to the template.
The db-level order_by cannot sort queryset by model's python method.
The solution is to introduce score field to Image model and recalculate it on every Vote update. Some sort of denormalization. When you will can to sort by it.

Categories

Resources