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.
Related
I want to know how I can add data to the Django integer field after the ins.save in my code. For example, if the donation field is equal to 3, after the ins.save, I want to add 1 to it and therefore it will be equal to 4. My code is down below.
Donate View:
def donate(request):
if request.method == "POST":
title = request.POST['donationtitle']
phonenumber = request.POST['phonenumber']
category = request.POST['category']
quantity = request.POST['quantity']
location = request.POST['location']
description = request.POST['description']
ins = Donation(title = title, phonenumber = phonenumber, category = category, quantity = quantity, location = location, description = description, user=request.user, )
ins.save()
return render(request,'donate.html')
You can try this.
from django.db.models import F
......
ins.save()
UserDetail.objects.filter(user=request.user).update(donations=F('donations') + 1)
This will increase the donations of request.user by 1 after ins.save()
Also instead of nullable donation field set default value to zero for your IntegerField in your UserDetail model
class UserDetail(models.Model):
donations = models.IntegerField(default=0)
You don't really need a donations field to count the number of donations of a user as it can be done by a query using the Count aggregation function [Django docs]. For example the below query will give you all users and their donation counts:
from django.db.models import Count
# Assuming 'User' is your user model name
users = User.objects.annotate(donation_count=Count('donation'))
for user in users:
print(user.username, user.donation_count)
# If we already have a user instance
donation_count = user.donation_set.count()
Moving further UserDetail appears to be a model that contains extra details about the user, but it has a problem that you are using a Foreign Key, instead of that you should be using a OneToOneField [Django docs] instead, and if you want donations in this model you can add a property that will make that query to this model:
class UserDetail(models.Model):
points = models.IntegerField(blank=True, null = True,)
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
) # A user detail without a user doesn't make much sense, hence no null or blank allowed
#property
def donations(self):
return self.user.donation_set.count()
I am building a budgeting web app for many users. The user creates accounts to track different values. The problem is that I cannot make a composite key and need the AccountName to be the primary key.
This poses a challenge. What if users make the same account name? This will happen as some users may make a "Cash" account. My solution to this problem is to name the account in the database the AccountName + the userid. How can I modify the users AccountName in the form to be AccountName + userid?
Desired AccountName examples in the database: Cash1, Cash2
models.py
class Account(models.Model):
DateCreated = models.DateTimeField()
AccountName = models.CharField(max_length= 100, primary_key=True)
UserID = models.ForeignKey(MyUser, on_delete=models.CASCADE)
Type = models.CharField(max_length= 20)
Balance = models.DecimalField(max_digits=19, decimal_places=8)
Value = models.DecimalField(max_digits=19, decimal_places=8)
Views.py
#login_required
def func_AccountsView(request):
# This filters users accounts so they can only see their own accounts
user_accounts = Account.objects.filter(UserID_id=request.user).all()
if request.method == 'POST':
form = AddAccount(request.POST)
if form.is_valid():
account = form.save(commit=False)
account.UserID = request.user
account.AccountName = AccountName + str(request.user) # WHY DOES THIS NOT WORK?
account.save()
return redirect('accounts')
else:
form = AddAccount()
else:
form = AddAccount()
data = {
'form':form,
'data': user_accounts
}
return render(request, 'NetWorth/accounts.html', data)
forms.py
class AddAccount(forms.ModelForm):
''' This is the form that handles additions of an account '''
class Meta:
model = Account
fields = ['AccountName','DateCreated', 'Type', 'Balance', 'Value']
Get AccountName from form input by getting it from cleaned_data then concat with request.user.pk. Don't forget to convert pk value into str otherwise you will getting TypeError exception
....
account.AccountName = form.cleaned_data.get('AccountName') + str(request.user.pk)
account.save()
request.user will not work because it is an object which contains other fields.
Also your AccountName variable does not represent anything thanks to Eby Sofyan's answer
To fix your problem replace,
account.AccountName = form.cleaned_data.get('AccountName') + str(request.user.id)
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 a model that reads from a pre-existing university database. The username is stored as the students id. I'm not able to add a new column to the database in a save or I could use a save function in my model.
class Student(models.Model):
student_id = models.IntegerField(db_column = 'Student_ID', primary_key = True)
statusid = models.IntegerField(db_column = 'StatusID')
fname = models.CharField(db_column = 'Student_First_Name', max_length = 35)
lname = models.CharField(db_column = 'Student_Last_Name_Formatted' , max_length = 40)
class Meta:
managed = False
db_table = '[Student]'
What i'm trying to do is take the username in a query and match it up with another field that is umich.edu/student_id as a primary key.
I'm defining the request in one function as:
def user_list(request):
student = Student.objects.all()
passing in the argument for students to my template with a POST to the form.
In my next view that gets posted
def criteria(request):
user = request.POST.get('student_id')
print(user)
student = CourseCriteria.objects.get(universitystudentid = request.POST.get('student_id'))
My print/post of user is coming through correctly as the student id, but I need to prepend umich.edu/ to the student_id in the queryset. How can I accomplish this without being able to add that field to my model?
I am not sure to understand your problem, I suppose that it is not as simple as:
def criteria(request):
user = request.POST.get('student_id')
student = CourseCriteria.objects.get(universitystudentid='umich.edu/%s'%user)
I'm trying to count the amount of times a user visits a page:
models.py:
class Request(models.Model):
user = models.ForeignKey(User)
view = models.CharField(max_length = 250)
visits = models.PositiveIntegerField()
views.py
def daygaps(request,*a, **kw):
request_counter = Request.objects.filter(
user__username = request.user.username, view = 'daygaps')
if request_counter:
request_counter[0].visits += 1
request_counter.update()
else:
Request.objects.create(
user = request.user,
visits = 1,
view = 'daygaps')
When a new user visits the page for the first time, 'visits' gets set to = 1. Each subsequent visit should iterate the count. The "else" block works fine, however "visits" stays at 1 and does not change with each new request a user makes.
I've tried the ".save()" method, but that throws an error because "request_counter" is a queryset.
You could do something like this:
models.py
class Request(models.Model):
user = models.ForeignKey(User)
view = models.CharField(max_length = 250)
visits = models.PositiveIntegerField(default=0)
views.py
def daygaps(request,*a, **kw):
request_counter = Request.objects.get_or_create(
user = request.user, view = 'daygaps')
request_counter.visits += 1 # or =F('visits')+1 to avoid a race condition as suggested by Moses Koledoye
request_counter.save()
OR
models.py
class Request(models.Model):
user = models.ForeignKey(User)
view = models.CharField(max_length = 250)
visits = models.PositiveIntegerField(default=1)
views.py
def daygaps(request,*a, **kw):
updated_count = Request.objects\
.filter(user = request.user, view = 'daygaps')\
.update(visits=F('visits')+1)
if not updated_count:
Request.objects.create(user = request.user, view = 'daygaps')
which avoids race conditions too and has an added advantage of not having to retrieve the object.
In general, I guess the second one is better if the update is simple enough to be implemented with F expressions.
Instead of using filter and indexing the list returned by the QuerySet, you could simply use .get to return the object:
from django.db.models import F
def daygaps(request,*a, **kw):
...
request_counter = Request.objects.get(
user__username = request.user.username, view = 'daygaps')
request_counter.visits = F('visits') + 1
request_counter.save()
You can wrap the logic in a try/except to handle DoesNotExist exceptions.
The F expression helps you manage race conditions. Read more: Updating attributes based on existing fields