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
Related
I have a similar issue to the post here in regards to setting up the receiver function for the paypal-ipn
What we are trying to do(I don't know this other person but I assume we stand together on this topic) is to understand how to deduce a path to receiving a paypal IPN signal in which we then can update the django database.
I have implemented the django-paypal API by following the directions here
Overall what I do is I create a view in my views.py as follows
def payment_page(request):
"""This fucntion returns payment page for the form"""
if not request.session.get('form-submitted', False):
return HttpResponseRedirect(reverse('grap_main:error_page'))
else:
amount = set_payment()
paypal_dict = {
"business": "business#gmail.com",
"amount": str(amount),
"item_name": "2017 USGF Championships",
"notify_url": "https://15b6b6cb.ngrok.io" + reverse('paypal-ipn'),
"return_url": "https://15b6b6cb.ngrok.io/Confirm",
"cancel_return": "https://15b6b6cb.ngrok.io/RegistrationForm",
}
form = PayPalPaymentsForm(initial=paypal_dict)
context = confirm_information()
context["amount"] = amount
context["form"] = form
request.session['form-submitted'] = False
valid_ipn_received.connect(show_me_the_money)
return render(request, "grap_main/payment.html", context)
Where my then I have payment.html which then create the paypal button simply by using the line as advised in the documentation
{{ form.render }}
Now I can receive a POST to the paypal url that I specified in the documentation however I don't know where I should put my signal function that will grab the IPNs once someone has completed a purchase.
def show_me_the_money(sender, **kwargs):
"""signal function"""
ipn_obj = sender
if ipn_obj.payment_status == ST_PP_COMPLETED:
print 'working'
else:
print "not working"
Right now I am calling this function in the view payment_page() however I know this is before the POST to the paypal and is not correct
I don't understand where I should call the show_me_the_money() function. I am used to creating a views in which is called from the html script as shown below
def register(request):
"""aquire information from new entry"""
if request.method != 'POST':
form = RegisterForm()
else:
if 'refill' in request.POST:
form = RegisterForm()
else:
form = RegisterForm(data=request.POST)
if form.is_valid():
form.save()
request.session['form-submitted'] = True
return HttpResponseRedirect(reverse('grap_main:payment_page'))
.html
<form action="{% url 'grap_main:register' %}" method='post' class="form">
{% csrf_token %}
{% bootstrap_form form %}
<br>
{% buttons %}
<center>
<button name='submit' class="btn btn-primary">Submit</button>
</center>
{% endbuttons %}
</form>
I believe I need to call the function after a person has completed a purchase however I don't know how to target that time window in my code. I want to make sure I handle a case in which the user once there are done paying don't always return to the merchant site.
Any help on this subject would not only benefit me but also the earlier poster. I hope to make a tutorial when I figure this out to help others that might get stuck as well.
Please note that I have also use ngrok to make sure my project is accessible to the paypal IPN service. I am using two urls.py files as well, i which the main one looks as so
urlpatterns = [
url(r'^paypal/', include('paypal.standard.ipn.urls')),
url(r'^admin/', admin.site.urls),
url(r'', include('grap_main.urls', namespace='grap_main')),
]
where the 'grap_main.urls' are all the specific views for my site, ex.
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^Confirm', views.confirm, name='confirm'),
url(r'^Events', views.events, name='events'),
.........]
[UPDATE]: Forgot to mention where and how to put the code that you wrote (the handler if you like). Inside the handlers.py file write it like this:
# grap_main/signals/handlers.py
from paypal.standard.ipn.signals import valid_ipn_received, invalid_ipn_received
#receiver(valid_ipn_received)
def show_me_the_money(sender, **kwargs):
"""Do things here upon a valid IPN message received"""
...
#receiver(invalid_ipn_received)
def do_not_show_me_the_money(sender, **kwargs):
"""Do things here upon an invalid IPN message received"""
...
Although signals (and handlers) can live anywhere, a nice convention is to store them inside your app's signals dir. Thus, the structure of your grap_main app should look like this:
project/
grap_main/
apps.py
models.py
views.py
...
migrations/
signals/
__init__.py
signals.py
handlers.py
Now, in order for the handlers to be loaded, write (or add) this inside grap_main/apps.py
# apps.py
from django.apps.config import AppConfig
class GrapMainConfig(AppConfig):
name = 'grapmain'
verbose_name = 'grap main' # this name will display in the Admin. You may translate this value if you want using ugettex_lazy
def ready(self):
import grap_main.signals.handlers
Finally, in your settings.py file, under the INSTALLED_APPS setting, instead of 'grap_main' use this:
# settings.py
INSTALLED_APPS = [
... # other apps here
'grap_main.apps.GrapMainConfig',
... # other apps here
]
Some side notes:
Instead of using standard forms to render the paypal button use encrypted buttons. That way you will prevent any potential modification of the form (mostly change of prices).
I was exactly in your track a few months ago using the terrific django-paypal package. However, I needed to upgrade my project to Python 3. But because I used the encrypted buttons I was unable to upgrade. Why? Because encrypted buttons depend on M2Crypto which doesn't support (yet) Python 3. What did I so? I dropped django-paypal and joined Braintree which is a PayPal company. Now, not only I can accept PayPal payments but also credit card ones. All Python 3!
This is is embarrassing but the problem was that I was not using a test business account... I implemented the answer nik_m suggegested as well so I would advise doing the same. Put the function:
def show_me_the_money(sender, **kwargs):
"""signal function"""
ipn_obj = sender
if ipn_obj.payment_status == ST_PP_COMPLETED:
print 'working'
else:
print "not working"
valid_ipn_received.connect(show_me_the_money)
in handlers.py
My app sits on Django 1.9.2 and Django REST Framework 3.3.2, with a single page app on the front-end. I must admit I'm new to Django but the docs are unmatched.
I'm trying to implement a custom PasswordResetForm view. My strategy is as follow:
User uses on a front-end form that POSTs data (email string) to an API endpoint (api/v1/password/reset) when she wants to reset her password.
If email is found in DB, send an email and return a successful response.
For part 1, here's the relevant code:
# urls.py
url(r'^api/v1/password/reset/?$', PasswordResetView.as_view(), name='password-reset-link')
-
# views.py
class PasswordResetView(GenericAPIView):
serializer_class = PasswordResetSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
# Use DRF serializer to validate data.
serializer = self.get_serializer(data=request.data)
# Ensure data is valid before proceeding.
if serializer.is_valid() == False:
return Response('error': serializer.errors, 'message': 'Error while attempting to reset password'}, status.HTTP_400_BAD_REQUEST)
else:
# Ensure the user exists.
existing_user = get_user_model().objects.filter(
email=serializer.validated_data.get('email')
)
if not existing_user:
return Response({'error': {'code': 'email_not_in_db'}}, status.HTTP_404_NOT_FOUND)
# Validated data fed to a child class of PasswordResetForm.
form = UserForgotPasswordForm(serializer.validated_data)
if form.is_valid():
path = os.path.join(os.path.dirname(os.path.abspath(__file__ + '../../')), 'templates/registration/password_reset_email.html')
try:
# Save form, effectively attempting to trigger mail sending.
# Unfortunately an exception gets thrown!
form.save(from_email='no-reply#abc.xyz', email_template_name=path, request=request)
return Response({'message': 'Password reset request sent'}, status=status.HTTP_200_OK)
except Exception as e:
return Response({'error': str(e), 'message': 'Error while attempting to reset password'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
-
# forms.py
class UserForgotPasswordForm(PasswordResetForm):
email = forms.EmailField(required=True, max_length=254)
class Meta:
model = CustomUser
fields = ('email',)
-
# my_app/templates/registration/password_reset_email.html
{% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your user account.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %}
{% block reset_link %}
{{ protocol }}://{{ domain }}{% url 'password-reset-link' uidb64=uid token=token %}
{% endblock %}
{% trans "Your username, in case you've forgotten:" %} {{ user.get_username }}
{% endautoescape %}
-
But I get the following error:
Reverse for 'password-reset-link' with arguments '()' and keyword arguments '{'uidb64': b'MTI1', 'token': '4g9-f370fd6ee48d90a40b67'}' not found. 1 pattern(s) tried: ['api/v1/password/reset/?$']
Any idea why? What am I missing? Thanks for your help.
In the url in your html you are passing a token and a uidb64:
{{ protocol }}://{{ domain }}{% url 'password-reset-link' uidb64=uid token=token %}
However, in your actual url, you do not reference the token or uidb64:
url(r'^api/v1/password/reset/?$', PasswordResetView.as_view(), name='password-reset-link')
I would recommend changing your url to:
url(r'^api/v1/password/reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', PasswordResetView.as_view(), name='password-reset-link')
Your URL named password-reset-link doesn't support the keyword arguments uidb64 and token that you're sending it. Those keyword arguments aren't just extra data, they need to match somewhere in the URL pattern.
According to the error, your URL pattern is:
api/v1/password/reset/?$
In order to support what you want you'd need to put two named arguments called <uidb64> and <token>. For example:
api/v1/password/reset/(?P<uidb64>[a-zA-Z0-9]+)/(?P<token>[a-zA-Z0-9]+)/?$
You'll have to adjust your regex to support the uidb64 and token formats.
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
I am attempting to install and test django-honeypot from sunlightlabs using the provided templates.
the application does not come accompanied with models nor views and I am confused as to how I am supposed to get the app to work. I have attempted to play with the urls to call the templates.
my mysite/mysite/urls.py follows:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
#url(r'^$', include('honeypot.urls')),
)
and my mysite/honeypot/urls.py follows:
from django.conf.urls.defaults import *
#from tagging.views import tagged_object_list
from django.conf.urls import patterns, include, url
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.shortcuts import render
from django.contrib import admin
urlpatterns = patterns('',
url(r'^$', render, dict(template_name='/home/mohrashak/attribute2/honeypot/templates/honeypot/webpage.html'), name='price'),
)
ROOT_URLCONF="honeypot.urls"
where webpage is
{% load honeypot %}
<form>
{% render_honeypot_field "field_name" %}
</form>
and my understanding is that something will be entered into the box be processed using the application. But there is no model. What am I missing?
You don't need to worry about models or views for django-honeypot. It is installed into the site-packages folder of your Python library so you don't have to write models/views/urls.py for you - these are all there in your pythonpath.
Make sure you read the online documentation for installing the app.
Here is the checklist:
Add honeypot to INSTALLED_APPS in settings.py;
Define HONEYPOT_FIELD_NAME which is the name to use for the honeypot field in your form. Try not to use 'honeypot' though as some bots will avoid it.
Make sure you add {% load honeypot %} in the top of your template using django-honeypot;
Then use the following tag in any form {% render_honeypot_field "field_name" %}
When your form is submitted - you can use the check_honeypot decorator from honeypot.decorators to check the value is present (or not by default) and correct.
Here is the example from the documentation to add to your view:
from honeypot.decorators import check_honeypot
#check_honeypot(field_name='hp_field_name')
def post_comment(request):
...
#check_honeypot
def other_post_view(request):
...
Edit1:
In response to your comment:
Yes;
Yes.
No - the nature of django-honeypot is that it prevents "form spam". So you have to have at least a form in your template. You should pass a form from a view to a template. More information in the documentation. I've written a near full example below.
Example of a contact form using django-honeypot
Note: This is untested.
This is an example that creates and shows a contact form (by going to a /contact/ URL) and then handles the form being submitted. Imagine we have used django_admin.py startapp contact_app
urls.py
Your urls file takes the /contact/ URL passes the request to our contact view.
from django.conf.urls import patterns, url
urlpatterns = patterns('',
url(r'^contact/$', 'contact_app.views.contact'),
)
contact_app/views.py
Your views file takes the request, and handles GET and POST requests. If the request is a GET it passes an empty form to the template. If a POST then it checks the form is valid and processes the form. We wrap it in the check_honeypot decorator to make sure it passes our django-honeypot test.
from contact_app.forms import ContactForm
from honeypot.decorators import check_honeypot
#check_honeypot
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render(request, 'contact.html', {
'my_form': form,
})
contact_app/forms.py
Here we specify the form fields that are going to be required and display in the template.
from django import forms
class ContactForm(forms.Form):
subject = forms.CharField(max_length=100)
message = forms.CharField()
sender = forms.EmailField()
cc_myself = forms.BooleanField(required=False)
templates/contact.html
From our view, we pass our form (called my_form) to our template and use django's templating language to render each field as a <p></p>. We also load the honeypot and insert the necessary field.
{% load honeypot %}
<form action="/contact/" method="post">{% csrf_token %}
{{ my_form.as_p }}
{% render_honeypot_field "field_name" %}
<input type="submit" value="Submit" />
</form>
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.