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
Related
I am learning Django, using function based views, and I am struggling with the following:
I have this path in urls.py
path('user/<str:username>',views.UserProjectList,name='user-projects')
that is supposed to show all the projects of the particular user (client). In order to reach it, username should be parameter of the function based view, however I am struggling how to write such view...
I have this:
def UserProjectList(request,username):
user = User.objects.get(username=username) #THIS IS WRONG and should return id of the user
#user = User.objects.filter(username=username) #also wrong
tag_list = ProjectTagsSQL.objects.all() #ProjectTagsSQL and ProjectSQL are connected
project_list = ProjectSQL.objects.filter(client=user) #ProjectSQL table has column client_id (pk is id in User) and table contains all the projects
context = {
'tagy' : tag_list,
'projecty' : project_list
}
return render(request, 'home_page/user_projects.html', context) #SHOULD THE PARAMETER BE INCLUDED HERE?
I tried to inspire with the code from class based view I found on the internets (thats is working for me but i didnt manage to connect it with ProjectTagsSQL as i managed in FBV, but that's a different problem) but i didnt manage
class UserProjectListView(ListView):
model = ProjectSQL
template_name = 'home_page/user_projects.html'
context_object_name = 'data'
def get_queryset(self):
user = get_object_or_404(User, username=self.kwargs.get('username'))
return ProjectSQL.objects.filter(client=user)
Could someone help me how to deal with such function based view please? As this solution its not working (will return nothing for any user)
Here is also the ProjectSQL model (and ProjectTagsSQL model) :
class ProjectSQL(models.Model):
id = models.AutoField(primary_key=True)
country = models.TextField()
city = models.TextField()
time_added = models.DateTimeField(default=timezone.now)
start_date = models.DateField()
end_date = models.DateField()
client = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
managed = False #https://docs.djangoproject.com/en/4.0/ref/models/options/
db_table = 'project'
class ProjectTagsSQL(models.Model):
id = models.IntegerField(primary_key=True)
project = models.ForeignKey(ProjectSQL, on_delete=models.CASCADE)
tag = models.ForeignKey(ProjectTagSQL, on_delete=models.CASCADE)
class Meta:
managed = False # https://docs.djangoproject.com/en/4.0/ref/models/options/
db_table = 'project_tags'
You need to write user.id so:
from django.shortcuts import get_object_or_404
def UserProjectList(request,username):
user = get_object_or_404(User,username=username)
tag_list = ProjectTagsSQL.objects.all()
project_list = ProjectSQL.objects.filter(client=user.id)
context = {
'tagy' : tag_list,
'projecty' : project_list
}
return render(request, 'home_page/user_projects.html', context)
Also, try to check template variables' name, whether you used same or not.
Note: Always append / at the end of every route so it should be path('user/<str:username>/'....
Note: Function based views are generally written in snake_case so it is better to name it as user_project_list instead of UserProjectList.
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 multiple choice field with a foreign key. I want to save which keeper was attending a training session and I want to list all keepers as a multiple choice field.
class AddAttendance(forms.ModelForm):
attendanceKeeper = Attendance.objects.only("keeper","present").all()
keeperValues = Attendance.objects.values_list("keeper__id", flat=True).distinct()
keeper = forms.ModelMultipleChoiceField(widget=forms.widgets.CheckboxSelectMultiple, queryset=Keeper.objects.filter(id__in=keeperValues, status=1))
class Meta:
model = Attendance
fields = ('keeper',)
def __init__(self, *args, **kwargs):
super(AddAttendance, self).__init__(*args, **kwargs)
self.initial["keeper"] = Keeper.objects.all()
However my problem is, I am not familiar how to handle a queryset in the view and how to loop through it and to save every instance with the value True or False.
I always get the value error that a queryset cannot be assigned
"Attendance.keeper" must be a "Keeper" instance
Can you help me how I access the queryset values and save them
def new_attendance(request, team_pk, package_pk):
if request.method == "POST":
form = AddAttendance(request.POST)
if form.is_valid():
for item in form:
attendance = item.save(commit=False)
attendance.keeper = get_object_or_404(AddAttendance.keeper)
attendance.team = get_object_or_404(Team, pk=team_pk)
attendance.created_date = timezone.now()
attendance.save()
return redirect(reverse('select_package', args=[package_pk, team_pk]))
else:
form = AddAttendance()
return render(request, 'attendance/new_attendance.html', {'form': form})
In the end I want to match keeper from the queryset and save True/False into the field present in my model
class Attendance(models.Model):
session = models.ForeignKey(Session)
keeper = models.ForeignKey(Keeper)
team = models.ForeignKey(Team)
present = models.BooleanField()
created_date = models.DateTimeField(default=timezone.now)
edited_date = models.DateTimeField(default=timezone.now)
You don't want a multiple choice field; you want a single choice. Only one keeper can be associated with each Attendance object.
You are doing a bunch of strange and unnecessary things here. You should remove most of this code, and use the ModelChoiceField which is the default for a ForeignKey. You don't want a checkbox widget either, since again that is for multiple choices; perhaps a radiobutton would be suitable.
class AddAttendance(forms.ModelForm):
class Meta:
model = Attendance
fields = ('keeper',)
widgets = {'keeper': forms.RadioSelect}
# remove the __init__ and the field definitions, you don't need them
...
form = AddAttendance(request.POST)
if form.is_valid():
attendance = item.save(commit=False)
attendance.team = get_object_or_404(Team, pk=team_pk)
attendance.created_date = timezone.now()
attendance.save()
return redirect(reverse('select_package', args=[package_pk, team_pk]))
There's no need to set the keeper explicitly in the view, since that's what the form is doing.
I would like to create a function in views.py to retrieve an information from this class:
class Vin(models.Model):
def __unicode__ (self):
return self.nom
name = models.CharField( max_length = 30)
year = models.CharField (max_length = 4)
appellation = models.ForeignKey(Appellation)
photo = models.CharField (max_length = 70)
quantity = models.IntegerField ()
released = models.IntegerField()
and retrieve it by its attribute "name".
So for instance, if i have a Wine ("chardonnay", 1990, ...) only retrieve chardonnay.
Thanks !
Your view function will be called after the user enters a URL that is in your urls.py. The url entered may provide you with the additional information you need to query the database and get information on the Vin object you need.
If you want a view to just return every Vin in your database. Something like this might be helpful for you:
def get_vins(request, *args, **kwargs):
vins = Vin.objects.all().values_list('name', flat=True)
return render(request, 'somepage.html', {"vins": vins})
Your question is really vauge and you should read the django documentation. This is a very basic question that all starters should learn.
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.