How to get the list of the authenticated users? - django

I would like to display the list of the authenticated users.
On the documentation: http://docs.djangoproject.com/en/dev/topics/auth/
class models.User
is_authenticated()ΒΆ
Always returns True. This is a way to tell if the user has been authenticated. ...
You can know on the template side is the current User is authenticated or not:
{% if user.is_authenticated %}
{% endif %}
But I didn't found the way the get the list of the authenticated users.
Any idea?

Going along with rz's answer, you could query the Session model for non-expired sessions, then turn the session data into users. Once you've got that you could turn it into a template tag which could render the list on any given page.
(This is all untested, but hopefully will be close to working).
Fetch all the logged in users...
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
from django.utils import timezone
def get_all_logged_in_users():
# Query all non-expired sessions
# use timezone.now() instead of datetime.now() in latest versions of Django
sessions = Session.objects.filter(expire_date__gte=timezone.now())
uid_list = []
# Build a list of user ids from that query
for session in sessions:
data = session.get_decoded()
uid_list.append(data.get('_auth_user_id', None))
# Query all logged in users based on id list
return User.objects.filter(id__in=uid_list)
Using this, you can make a simple inclusion template tag...
from django import template
from wherever import get_all_logged_in_users
register = template.Library()
#register.inclusion_tag('templatetags/logged_in_user_list.html')
def render_logged_in_user_list():
return { 'users': get_all_logged_in_users() }
logged_in_user_list.html
{% if users %}
<ul class="user-list">
{% for user in users %}
<li>{{ user }}</li>
{% endfor %}
</ul>
{% endif %}
Then in your main page you can simply use it where you like...
{% load your_library_name %}
{% render_logged_in_user_list %}
EDIT
For those talking about the 2-week persistent issue, I'm assuming that anyone wanting to have an "active users" type of listing will be making use of the SESSION_EXPIRE_AT_BROWSER_CLOSE setting, though I recognize this isn't always the case.

Most reliable solution would only be the something you store when user logs in or logs out. I saw this solution and i think its worth sharing.
models.py
from django.contrib.auth.signals import user_logged_in, user_logged_out
class LoggedUser(models.Model):
user = models.ForeignKey(User, primary_key=True)
def __unicode__(self):
return self.user.username
def login_user(sender, request, user, **kwargs):
LoggedUser(user=user).save()
def logout_user(sender, request, user, **kwargs):
try:
u = LoggedUser.objects.get(user=user)
u.delete()
except LoggedUser.DoesNotExist:
pass
user_logged_in.connect(login_user)
user_logged_out.connect(logout_user)
views.py
logged_users = LoggedUser.objects.all().order_by('username')

Sounds similiar with this solution, you can create a custom middleware to do it. I found awesome OnlineNowMiddleware here.
Where you will get these;
{{ request.online_now }} => display all list of online users.
{{ request.online_now_ids }} => display all online user ids.
{{ request.online_now.count }} => display total online users.
How to set up?
Create file middleware.py where location of settings.py has been saved, eg:
projectname/projectname/__init__.py
projectname/projectname/middleware.py
projectname/projectname/settings.py
Then following this lines;
from django.core.cache import cache
from django.conf import settings
from django.contrib.auth.models import User
from django.utils.deprecation import MiddlewareMixin
ONLINE_THRESHOLD = getattr(settings, 'ONLINE_THRESHOLD', 60 * 15)
ONLINE_MAX = getattr(settings, 'ONLINE_MAX', 50)
def get_online_now(self):
return User.objects.filter(id__in=self.online_now_ids or [])
class OnlineNowMiddleware(MiddlewareMixin):
"""
Maintains a list of users who have interacted with the website recently.
Their user IDs are available as ``online_now_ids`` on the request object,
and their corresponding users are available (lazily) as the
``online_now`` property on the request object.
"""
def process_request(self, request):
# First get the index
uids = cache.get('online-now', [])
# Perform the multiget on the individual online uid keys
online_keys = ['online-%s' % (u,) for u in uids]
fresh = cache.get_many(online_keys).keys()
online_now_ids = [int(k.replace('online-', '')) for k in fresh]
# If the user is authenticated, add their id to the list
if request.user.is_authenticated:
uid = request.user.id
# If their uid is already in the list, we want to bump it
# to the top, so we remove the earlier entry.
if uid in online_now_ids:
online_now_ids.remove(uid)
online_now_ids.append(uid)
if len(online_now_ids) > ONLINE_MAX:
del online_now_ids[0]
# Attach our modifications to the request object
request.__class__.online_now_ids = online_now_ids
request.__class__.online_now = property(get_online_now)
# Set the new cache
cache.set('online-%s' % (request.user.pk,), True, ONLINE_THRESHOLD)
cache.set('online-now', online_now_ids, ONLINE_THRESHOLD)
Finally update your MIDDLEWARE inside file of projectname/projectname/settings.py:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
....
# Custom middleware
'projectname.middleware.OnlineNowMiddleware',
]
For other condition, you can also check the current user is online or not with:
{% if request.user in request.online_now %}
{# do stuff #}
{% endif %}

There is no easy, built-in way to do what you want that I know of. I'd try a combination of expiring sessions and filtering on last_login. Maybe even write a custom manager for that.

Related

How to customize django rest auth password reset email content/template

In django rest_auth password reset, default email content look like following:-
You're receiving this email because you requested a password reset for your user account at localhost:8000.
Please go to the following page and choose a new password:
http://localhost:8000/api/reset/Kih/89a-23809182347689312b123/
Your username, in case you've forgotten: test
Thanks for using our site!
The localhost:8000 team
How to customize content of this email ?
I recently needed to implement the same thing in one of my projects and could not find a thorough answer anywhere.
So I'm leaving my solution here for anyone who needs it in the future.
Expanding on mariodev's suggestion:
1. Subclass PasswordResetSerializer and override save method.
yourproject_app/serializers.py
from django.conf import settings
from rest_auth.serializers import PasswordResetSerializer as _PasswordResetSerializer
class PasswordResetSerializer(_PasswordResetSerializer):
def save(self):
request = self.context.get('request')
opts = {
'use_https': request.is_secure(),
'from_email': getattr(settings, 'DEFAULT_FROM_EMAIL'),
###### USE YOUR TEXT FILE ######
'email_template_name': 'example_message.txt',
'request': request,
}
self.reset_form.save(**opts)
2. Configure AUTH_USER_MODEL
yourproject/settings.py
###### USE YOUR USER MODEL ######
AUTH_USER_MODEL = 'yourproject_app.ExampleUser'
3. Connect custom PasswordResetSerializer to override default
yourproject/settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':
'yourproject_app.serializers.PasswordResetSerializer',
}
4. Add the path to the directory where your custom email message text file is located to TEMPLATES
yourproject/settings.py
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'yourproject/templates')],
...
}
]
5. Write custom email message (default copied from Django)
yourproject/templates/example_message.txt
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset
for your user account at {{ site_name }}.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% trans "Thanks for using our site!" %}
{% blocktrans %}The {{ site_name }} team{% endblocktrans %}
{% endautoescape %}
UPDATE: This solution was written for an older version of django-rest-auth (v0.6.0). As I can tell from the comments, it seems there have been some updates made to the source package that more readily handle custom email templates out-of-box. It is always better to use methods defined in a package rather than overriding them like in my solution. Though once a necessity, it may not be so any longer.
You can inherit PasswordResetSerializer and override the get_email_options method. For example:
from rest_auth.serializers import PasswordResetSerializer
class CustomPasswordResetSerializer(PasswordResetSerializer):
def get_email_options(self):
return {
'subject_template_name': 'registration/password_reset_subject.txt',
'email_template_name': 'registration/password_reset_message.txt',
'html_email_template_name': 'registration/'
'password_reset_message.html',
'extra_email_context': {
'pass_reset_obj': self.your_extra_reset_obj
}
}
You need to hook up your own reset password serializer (PASSWORD_RESET_SERIALIZER) with customized save method.
(ref: https://github.com/Tivix/django-rest-auth/blob/v0.6.0/rest_auth/serializers.py#L123)
Unfortunately you need to override the whole save method, due to how the e-mail options are used. We we'll make it a bit more flexible in the next release (0.7.0)
A simple solution is
Create over templates directory:
-templates
-registration
password_reset_email.html
with content you want.
Django rest-auth use django.contrib.auth templates.
So for the dj-rest-auth, this is how I did it:
from django.contrib.auth.tokens import default_token_generator
from django.contrib.sites.shortcuts import get_current_site
from django.urls.base import reverse
from allauth.account import app_settings
from allauth.account.adapter import get_adapter
from allauth.account.utils import user_pk_to_url_str, user_username
from allauth.utils import build_absolute_uri
from dj_rest_auth.forms import AllAuthPasswordResetForm
from dj_rest_auth.serializers import PasswordResetSerializer
class CustomAllAuthPasswordResetForm(AllAuthPasswordResetForm):
def save(self, request, **kwargs):
current_site = get_current_site(request)
email = self.cleaned_data['email']
token_generator = kwargs.get('token_generator',
default_token_generator)
for user in self.users:
temp_key = token_generator.make_token(user)
# save it to the password reset model
# password_reset = PasswordReset(user=user, temp_key=temp_key)
# password_reset.save()
# send the password reset email
path = reverse(
'password_reset_confirm',
args=[user_pk_to_url_str(user), temp_key],
)
url = build_absolute_uri(None, path) # PASS NONE INSTEAD OF REQUEST
context = {
'current_site': current_site,
'user': user,
'password_reset_url': url,
'request': request,
}
if app_settings.AUTHENTICATION_METHOD != app_settings.AuthenticationMethod.EMAIL:
context['username'] = user_username(user)
get_adapter(request).send_mail('account/email/password_reset_key',
email, context)
return self.cleaned_data['email']
class CustomPasswordResetSerializer(PasswordResetSerializer):
#property
def password_reset_form_class(self):
return CustomAllAuthPasswordResetForm
# settings.py
REST_AUTH_SERIALIZERS = {
'PASSWORD_RESET_SERIALIZER':
'api.users.api.serializers.CustomPasswordResetSerializer',
}
By passing None to build_absolute_uri instead of the original request, it will take the value you have in django.contrib.sites module with SITE_ID=1. So whatever you have defined as your domain in the Django admin will now be the domain in the reset URL. This makes sense if you want to have the password reset URL point to your frontend, that might be a React application running on a different domain.
Edit:
My PR regarding this issue was merged, with the next release this will be possible to set in your settings. Checkout the docs for dj-rest-auth to see which setting you need to set.
if you want to use a html email template, an update to Brian's answer would be to add
'html_email_template_name': 'account/email/example_message.html',
just below
###### USE YOUR TEXT FILE ######
'email_template_name': 'account/email/example_message.txt',
this way you can the email with a html template
You can see why this happens by inspecting the send_mail method of the PasswordResetForm class
class PasswordResetForm(forms.Form):
email = forms.EmailField(label=_("Email"), max_length=254)
def send_mail(self, subject_template_name, email_template_name,
context, from_email, to_email, html_email_template_name=None):
"""
Send a django.core.mail.EmailMultiAlternatives to `to_email`.
"""
subject = loader.render_to_string(subject_template_name, context)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
body = loader.render_to_string(email_template_name, context)
email_message = EmailMultiAlternatives(subject, body, from_email, [to_email])
if html_email_template_name is not None:
html_email = loader.render_to_string(html_email_template_name, context)
email_message.attach_alternative(html_email, 'text/html')
email_message.send()```
Create directory with path as following in your template folder
templates/admin/registration/
Now copy all files in django/contrib/admin/templates/registration/ into this directory you just created. You can find this directory where you have installed django. In linux, it can be find here
/usr/local/lib/python2.7/dist-packages/django/contrib/admin/templates/registration
You will need root priviliges for accessing this.
Now when you will send email, templates in you just copied in your project will be used.
This link might be helpful. With it I was able to find where the email templates were and how to customize them.
You can find the info at the bottom of the page under
Customize the email message
http://www.sarahhagstrom.com/2013/09/the-missing-django-allauth-tutorial/#Customize_the_email_message

how can I ensure a user will not delete another user object in my website [Django]

I wrote a function that allows the user to delete his article on a blog website. The problem is, if he plays a little with the url, he can access to another article and delete it.
What is the common strategy to avoid such cases with django?
here are the codes I wrote for the fonction:
views.py
def delete_article(request, id):
deleted = False
logged_user = get_logged_user_from_request(request) #that line allow to ensure that the user is connected. I use the session to achieve that instead of extending the User model
offer = get_object_or_404(Offer, id=id)
if request.method == 'POST':
offer.delete()
deleted = True
return render(request, 'offers/delete_article.html', locals())
urls.py
urlpatterns = patterns('article.views',
url(r'^send_article$', 'send_article', name='send_article'),
url(r'^my_articles$', 'show_my_articles', name='my_articles'),
url(r'^article/(?P<id>\d+)$', 'read', name='read'),
url(r'^articles$', 'show_articles', name='articles'),
url(r'^search_article$', 'search', name='search'),
url(r'^delete_article/(?P<id>\d+)$', 'delete_offer', name='delete_offer'),
)
delete_article.html
{% if not deleted %}
Hey, are you sure you want to delete {{ article.title }}?
<form method="POST">
{% csrf_token %}
<button type="submit" class="deleting_offer_button">delete</button>
</form>
{% elif deleted %}
<p>the article was successfully deleted</p>
get back to the homepage<br />
{% endif %}
As you can see, if the user change the numer of the id in the url, he can delete other article when he is directed to the confirmation of deleting page.
What webmasters are doing to ensure users cannot interfere with objects of other users?
HttpResponseForbidden can be used here which uses a 403 status code. A 403 response generally used when authentication was provided, but the authenticated user is not permitted to perform the requested operation.
Assuming you have author as an foreign key in Offer model, you can change your views like this:
In your views.py you have to import :
from django.http import HttpResponseForbidden
And then in your delete_article method use this code
offer = get_object_or_404(Offer, id=id)
if offer.author != request.user:
return HttpResponseForbidden()
When you get the article/offer. Make sure that the owner of that article is the authenticated user.
I'm not sure what your models look like but it would be something like
offer = get_object_or_404(Offer, id=id, author=logged_user)
This way if they don't own the article, it will 404

How to use models associated with a user in Django when rendering an HTML page

I'm in the learning stages of django. I just dived into a project to learn the framework and am having a series of questions throughout the process.
I basically want to have individual pages for users who create a task list and have them post tasks to their own page.
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class UserProfile(models.Model):
# This line is required. Links UserProfile to a User model instance.
user = models.OneToOneField(User)
# The additional attributes we wish to include.
website = models.URLField(blank = True)
# Override the __unicode__() method to return out something meaningful!
def __unicode__(self):
return self.user.username
class TaskItem(models.Model):
taskn = models.CharField(max_length = 400)
usern = models.ForeignKey(User)
In my template, if the user has their task entered, how do I call it to render onto the page?
My View:
def profile_page(request, username):
user = User.objects.get(username=username)
taskitems = user.taskn_set.all()
return render_to_response('profile.html', {}, context)
Current issue:
'User' object has no attribute 'taskn_set'
{{ request.user.taskitem_set.all }} would give you all the related task items. Now, to display it in your template:
{% for task_item in user.taskitem_set.all %}
{{ task_item.task_n }}
{% endfor %}
would display the list of tasks.
Here is the documentation on reverse-queries on foreign key (related_name) Also, read this
you would do something like this:
{% for task in user.taskitem_set.all %}
{{ task.task_n }}
{% endfor %}
This will fetch all TaskItem instances related to your user. (notice the extra database query)
While i don't know how your view works, i will assume that you are making the right checks to make sure that every user can only see his own tasks.
One performance trick you will find most useful is to use prefetch_related('taskitem_set'), this will prefetch the TaskItem instances as long as your UserProfile instance with one query:
user = User.objects.filter(id=user_id).prefetch_related('taskitem_set')
You can tune the code to match your preferences.
Hope this helps!

Show information based on users

I have a class called Schedule with a field (is this correct?) I've set using
admins = models.ManyToManyField(User). This field contains a list of users I can select multiples of.
In the view for the schedule, I show a bunch of information. I would like to show a couple additional things based on if the currently logged in user is included in the admins of that schedule being viewed.
According to Django philosophy, you should have your business logic within views and presentation logic in the template. So the computation if the logged user is among the admins should be done in a view, and if the user is, then what is displayed should be determined in the template. You can accomplish that by:
# views.py
def schedule(request, id):
schedule = get_object_or_404(Schedule, pk=id)
if request.user.is_authenticated():
is_admin = schedule.admins.filter(pk=schedule.pk).exists()
else:
is_admin = False
data = {
'schedule': schedule,
'is_admin': is_admin,
}
return render_to_response('template.html', data)
# template.html
{% if is_admin %}
<p>You are an admin of the schedule!</p>
{% else %}
<p>Sorry. You are not an admin of the schedule</p>
{% endif %}

How do I write a template tag that shows if a user belongs to an app when that app isn't part of the context?

I have an app that has a FK to USER.
In ALL templates I need a template tag that tests to see if the logged in user is in that app.
For instance...
On every page I want to show a template block if the user is in the customer.approved set.
I've been playing with {% if approved in customer.approved %} - but I'm not getting anywhere.
Any suggestions welcome.
I think what you need is a filter, that checks if user is in your model (FK). So in your application_tags.py, you would do something like:
from django import template
from app.models import MyModel
register = template.Library()
#register.filter
def belongs_to_app(user):
if user.is_authenticated(): # Could be AnonymousUser
try:
# Check if there is a object with FK to logged user
m = MyModel.objects.get(user=user)
return True
except MyModel.DoesNotExist:
return False
return False
And in your template:
{% if user|belongs_to_app %}
....
{% endif %}
if yo set user perms you can write a context_processor so that:
def user_is_authorised(request):
if request.user.has_perm('myapp.can_view'):
return {'approved' : True}
else:
return {'approved' : False}
& include this in the CONTEXT_PROCESSORS settings.py tuple as 'myapp.context_processors.py'
more reading at:
https://docs.djangoproject.com/en/dev/topics/auth/#handling-object-permissions &
https://docs.djangoproject.com/en/dev/ref/templates/api/#django-contrib-auth-context-processors-auth
alternatively you can set the application in a session variable and then check this against your customer approved foreign key.