Django Display latest emails to certain user in Template - python

My django app I have created relies heavily on emails that are sent like this:
from django.core.mail import send_mail
send_mail(
'Subject here',
'Here is the message.',
'from#example.com',
['to#example.com'],
fail_silently=False,
)
Lets say that I keep sending an email to an user's email like this:
post = get_object_or_404(Post, pk=pk)
post_title = post.title
author_of_post = post.author
post_email = author_of_post.email
send_mail(
'An User Comment on Your Post',
'''Dear User, ''' '''
Your post, ''' + post_title + ''' was comment on by an user. Want to reply and check it out?
--From the People at Site'''
,
'randomemailuser#domain.com',
[post_email],
)
Now I want to add a notifications area, where it will display all the latest emails sent to the user, in the example of above post_email. So how would I do this. To sum up, I want to have a template where an user can see the latest emails sent to their account, sort of like a notification area. Thanks.

If you want to keep track of the emails that you are sending, you will need to save this information in your database, which means introducing a model. Something like this should do the trick:
#models.py
class SentEmail(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
email_subject = models.CharField(max_length=255)
...
You can obviously attach any additional info onto this model that you want (time sent, email body etc.). Then when you send an email, you'll also want to save a model instance, e.g:
from .models import SentEmail
...
send_mail( ... ) # all the stuff you had before
SentEmail.objects.create(
email_subject="An User Comment on Your Post"
user=whoever_the_user_is
)
Then you just create a view and template to display this information the same way you would any other view.
An alternative approach
Some third party services allow you to manage all transactional emails (sendgrid, mailgun etc.), and they may provide an API for you to fetch all emails sent to a particular user. This would allow you to achieve the kind of thing you described above, but to be honest, I think the approach I suggested above would be alot simpler.

Related

Django Password Reset Via Email

I'm trying to implement password reset by sending an email to the user with a link which will redirect him/her to a new password form.
I took by example this question and this site.
But my problem is a bit different. I don't have a local database containing the users, so I cannot perform operations over their attributes. I receive user data via an API (user id, user email, user password).
So, which is the best way to generate a unique link to send via email to user so that this link would tell me who the user is and allow me to reset his/her password? And also, how could I redirect it in urls.py?
I wish that this link could be used only a single time.
My views.py is like this:
def password_reset_form(request):
if request.method == 'GET':
form = PasswordResetForm()
else:
form = PasswordResetForm(request.POST)
if form.is_valid():
email = form.cleaned_data['email']
content_dict = {
'email': email,
'domain': temp_data.DOMAIN,
'site_name': temp_data.SITE_NAME,
'protocol': temp_data.PROTOCOL,
}
subject = content_dict.get('site_name')+ ' - Password Reset'
content = render_to_string('portal/password_reset_email.html', content_dict)
send_mail(subject, content, temp_data.FIBRE_CONTACT_EMAIL, [email])
return render(request, 'portal/password_reset_done.html', {'form': form,})
return render(request, 'portal/password_reset_form.html', {'form': form,})
And the template the e-mail I'm sending is:
{% autoescape off %}
You're receiving this e-mail because we got a request to reset the password for your user account at {{ site_name }}.
Please go to the following page and choose a new password:
{% block reset_link %}
{{ protocol }}://{{ domain }}/[***some unique link***]
{% endblock %}
If you didn't request a password reset, let us know.
Thank you.
The {{ site_name }} team.
{% endautoescape %}
Thanks, guys.
Don't reinvent the wheel. You should use django-allauth for this kind of stuff. The library is well maintained and under active development.
The problem is not to generate unique link, you need to store somewhere info about user id or email, and generated token. Otherwise you will not know which user should use which token. After user resets his password you can delete his record (his token).
You can write the most simple model even in sqlite, which basically could look like this:
class UserTokens(model.Models):
email = models.EmailField(max_length=50)
token = models.CharField(max_length=50)
Afterwards when you send mail make something like this:
def password_reset_form(request):
#your logic here
# form post
usr, created = UserToken.objects.get_or_create(email=form.email)
if usr.token:
#send this token to him
else:
usr.token = ''.join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(50))
usr.save()
#also send this token to him
Then you create a new view or api view which searches for that token, if found, let him reset the password. If not, raise a 404 error, or just let him know that there is no such link to reset password.
Please not that, this was written from my phone so care for typos.
PS. you also asked about urls
just make something like this:
url(r'unsubscribe/(?P<quit_hash>[\w\d]+)/$', 'quit_newsletter_or_somethin', name='quit_newsletter_or_somethin')
I’d suggest to borrow the general logic from Django and adapt it to your specific conditions:
PasswordResetTokenGenerator code — pretty well-documented, very useful
password reset docs (probably known information, though)
password reset view implementation
As you can see from PasswordResetTokenGenerator._make_token_with_timestamp(), its algo relies on user’s last login timestamp, so the APIs you consume would need to accommodate that.
You could import the same utility functions used by the above—where cryptography is concerned it’s better to rely on well-tested solutions. Deep internals are prone to change without a release note though when you update to newer Django versions, so take care.
You could also look into simply storing some carefully randomly generated reset codes along with usernames in your local DB and deleting them when user accesses the reset form, but that is less elegant while being more brittle and infosec-issue prone.

Admin(only) registration of users, Flask-Security

I'm currently building a login for a webapp using Flask-Security (which includes Flask-WTForms, Flask-SQLalchemy and Flask-Login). I've been able to fairly painlessly set up the majority of my login flow, including forgotten password; however I want to make it so that the only way users can be registered is through a page only accessible to the admins. I've managed to configure Roles (Admin, User) and set up the following view:
#app.route('/adminregister')
#roles_accepted('admin')
def adminregister():
return render_template('*')
This creates the portal that is successfully limited to accounts with an Admin role. I'm unsure how to proceed for here however, as Flask-security has no built in means to enable what I'm trying to do.
I've overridden RegisterForm already to enforce password rules through a regexp:
# fixed register form
class ExtendedRegisterForm(RegisterForm):
password = TextField('Password', [validators.Required(), validators.Regexp(r'(?=.*?[0-9])(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[$-/:-?{-~!"^_`\[\]])')])
Basically I want a form, located at /adminregister, that when visited by an admin allows for the entry of an email address, at which point first the user is created in the database with a random and secure password, and then a similar process to a forgotten password happens and a 1 time password code is created to reset the password.
Useful things I've looked at:
Within flask-security/views.py there is the forgotten passsword code:
def forgot_password():
"""View function that handles a forgotten password request."""
form_class = _security.forgot_password_form
if request.json:
form = form_class(MultiDict(request.json))
else:
form = form_class()
if form.validate_on_submit():
send_reset_password_instructions(form.user)
if request.json is None:
do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email))
if request.json:
return _render_json(form, include_user=False)
return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'),
forgot_password_form=form,
**_ctx('forgot_password'))
Within flask_security/registerable.py there is the code for register_user
def register_user(**kwargs):
confirmation_link, token = None, None
kwargs['password'] = encrypt_password(kwargs['password'])
user = _datastore.create_user(**kwargs)
_datastore.commit()
if _security.confirmable:
confirmation_link, token = generate_confirmation_link(user)
do_flash(*get_message('CONFIRM_REGISTRATION', email=user.email))
user_registered.send(app._get_current_object(),
user=user, confirm_token=token)
if config_value('SEND_REGISTER_EMAIL'):
send_mail(config_value('EMAIL_SUBJECT_REGISTER'), user.email, 'welcome',
user=user, confirmation_link=confirmation_link)
return user
I want to somehow combine these two, so that upon submission of a form with the sole field "Email" at '/adminregister' the email is added with a secure, random password in the database and the email address is sent an email with a link to change there password (and ideally a message explaining). I'm not even sure where I would add such code, as there is nothing to specifically override, especially as I can't find a way to override RegisterForm to have FEWER fields and the same functionality.
The structure of my code is in line with the flask-security documentation's quickstart.
Thank you in advance for any guidance you can offer.
I ended up using a work around as follows:
I enabled registration but limited registration view to users with an admin role.
I used del form.password in views -> register to no longer send the form with a password field.
I did the following in .registerable, generating a random password to fill the table.
kwargs['password'] = encrypt_password(os.urandom(24))
Upon admin entry of an email in the registration form, I had confimable enabled. This means the user would immediatly get an email to confirm their account and explaining they'd been registered. Upon confirmation they are redirected to the forgotten password page and asked to change their password (which is limited based on security).
If anyone comes up with a more direct way I'd appreciate it. I'm leaving this here in case anyone has the same problem.
The register process creates a signal with blinker that you can access like this:
from flask.ext.security.signals import user_registered
#user_registered.connect_via(app)
def user_registered_sighandler(app, user, confirm_token):
user_datastore.deactivate_user(user)
db.session.commit()
Which will deactivate any newly registered users.
I know this is an ancient question, but I think I have an elegant answer.
first import register_user
from flask_security.registerable import register_user
Then since you do not want just anyone to register ensure registerable is disabled (though disabled is the default so you can omit this) and since you want to send confirmation email, enable confirmable, and changeable for users to change their paswords
app.config['SECURITY_CONFIRMABLE'] = True
app.config['SECURITY_REGISTERABLE'] = False
app.config['SECURITY_RECOVERABLE'] = True
Then, you can do your create your user registration view and decorate it with role required. I have used my own custom registration form so I have had to go an extra mile to check if user already exists and return an error accourdingly
#app.route('/admin/create/user', methods=['GET', 'POST'])
#roles_required('admin')
def admin_create_user():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate_on_submit():
email = form.email.data
password = form.password.data
user_exists = session.query(User).filter_by(email=email).first()
if user_exists:
form.email.errors.append(email + ' is already associated with another user')
form.email.data = email
email = ''
return render_template('create-user.html', form = form)
else:
register_user(
email=email,
password = password)
flash('User added successfully')
return render_template('create-user.html', form = form)
Also see flask-security - admin create user, force user to choose password
Here's another solution I found after poking through flask-security-too. I made an admin create user form, and simply add the following code after creating the user in the database:
from flask_security.recoverable import send_reset_password_instructions
# my code is maintains self.created_id after creating the user record
# this is due to some complex class involved which handles my crudapi stuff
# your code may vary
user = User.query.filter_by(id=self.created_id).one()
send_reset_password_instructions(user)

Django Send emails to all users in the database table

I have started django building my first app tutorials, i have to send email to all my users store in the database table on some special Ocations. i have searched on google and found many apis but found it very hard to configure with my app.
here is my model.py
class Users(models.Model):
UserID = models.IntegerField(verbose_name='User ID',max_length=255,primary_key=True)
UserName = models.CharField(verbose_name='User Name',max_length=254,null=True,blank=True)
Email = models.EmailField(verbose_name='Email',max_length=254,null=True,blank=True)
Phone = models.CharField(verbose_name='Phone Number',max_length=254,null=True,blank=True)
i want to have a function here which should get all users one-by-one and send email also tells the status weather the email has been sent or not.
battery's answer is ok, but i would do this way:
recievers = []
for user in Users.objects.all():
recievers.append(user.email)
send_mail(subject, message, from_email, recievers)
this way, you will open only once connection to mail server rather than opening for each email.
Sending email is very simple.
For your Users model this would be:
for user in Users.objects.all():
send_mail(subject, message, from_email,
user.Email)
Checkout Django's Documentation on send emails for more details.
Would be useful if you mention what problem you are facing if you've tried this.
Also note, IntegerField does not have a max_length argument.
for user in Users.objects.all():
send_mail(subject, message, from_email,
user.Email)
This is the best solutions and it works well

django: keep each users data separate

I am trying to workout how / the best, most secure way to keep a user's data separate within a django site that I need to write.
Here is an example of what I need to do...
example app ToDoList
Using django contrib.auth to manage users / passwords etc, I will have the following users
tom
jim
lee
There will be a ToDo model (in my real app there will be additional models)
class ToDo(models.Model):
user = models.ForeignKey(User)
description = models.CharField(max_length=20)
details = models.CharField(max_length=50)
created = models.DateTimeField('created on')
The issue that I am having - and may be over thinking this: How would this be locked down so tom can only see Tom's todo list, lee can only see his todo list and so on...
I have seen a few posts stating that you could use filter in every query, or use urls, so the url could look like www.domain.com/username/todo
But either way I am not sure if this is the right way / best way, or bonkers in terms of stopping users seeing each others data
cheers
Richard
One approach is to filter the ToDo items by the currently logged in user:
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from your_app.models import ToDo
#login_required
def todos_for_user(request):
todos = ToDo.objects.filter(user=request.user)
return render(request, 'todos/index.html', {'todos' : todos})
This locks down the view for authenticated users only, and filtering by the logged in user from the request, another user, even if logged in, can't access another user's ToDo records. Hope that helps you out.
Make url like www.domain.com/username/todo is one way to implement it, but it doesn't guarantee you achieve security.
What you should do keep your user's login information in a session data after user login, and every time you check certain view,
check whether that particular user has right to see this view.
using user's login info (ID, or username) when querying user's Todo list.
And I guess this link will help you to do your job.
Sessions, Users, and Registration.

Django Admin Customizing

I am designing an admin interface where invite mails will be sent to users. My Invitation model is ready & in my invitation admin interface I am able to see my added users for which the admin can send email invites.
now I want to customize this a bit. I want to add for each row a SEND button which will actually send an email to that user. Sending email function etc. are all ready. I am not getting as to how I can customize this admin template to add a send button. Can someone help ?? or atleast point me in the right direction...
P.S: it need not be a send button, it could be part of "action" dropdown where for the selected users I can jointly send emails.
Regarding the send button for each row, you can give your model (or ModelAdmin) a new function which returns the corresponding HTML pointing to your views (or calling corresponding AJAX functions). Just add your function to the ModelAdmin's "list_display" and make sure that HTML tags don't get escaped:
class MyModelAdmin(admin.ModelAdmin):
...
list_display = ('name', 'email', 'sender', 'send_email_html')
def send_email_html(self, obj):
# example using a javascript function send_email()
return 'Send Now' % obj.id
send_email_html.short_description = 'Send Email'
send_email_html.allow_tags = True
Regarding the use of an action, define "actions" in your ModelAdmin as a list containing your function which takes modeladmin, request, queryset as parameters:
def send_email_action(modeladmin, request, queryset):
whatever_you_want_to_do_with_request_and_queryset
send_email.short_description = 'Send email'
class MyModelAdmin(admin.ModelAdmin):
...
actions = [
send_email_action
]
My solution below is for adding the "send invite" action in admin interface
"Send Invite" action
You can refer to the django admin-actions documentation here.
Here is what your admin.py should look like:
from django.contrib import admin
from myapp.models import MyModel
from django.core.mail import send_mail
class MyModelAdmin(admin.ModelAdmin):
actions = ['send_invite']
def send_invite(self, request, queryset):
# the below can be modified according to your application.
# queryset will hold the instances of your model
for profile in queryset:
send_email(subject="Invite", message="Hello", from_eamil='myemail#mydomain.com', recipient_list=[profile.email]) # use your email function here
send_invite.short_description = "Send invitation"
admin.site.register(MyModel, MyModelAdmin)
I have not tested this code, but it is pretty much what you need. Hope this helps.

Categories

Resources