Proper way to catch KeyError in Django? - django

I am creating an API where a URL is sent to the server. I want to get the username, etc from the URL. I use the following code to do that:
try:
username = request.REQUEST['username']
message = request.REQUEST['message']
time = request.REQUEST['time']
except KeyError:
...
However, there are times when the URL does not have a username, message, time, etc in it. In that case, a KeyError is raised. I want to be able to catch this and know which parameter was missing so I can issue an error response code that tells the user which parameter was missing. In the except area, is there a way to determine where the code failed?

Not cleanly. Use a default of None and test after.
try:
username = request.REQUEST.get('username', None)
...
except ...:
...
else:
if username is None:
...

wouldn't this work? No need for exceptions
if 'username' in request.REQUEST:
username = request.REQUEST['username']
else:
#username not found

Related

Django message history

I wonder if you have any ideas on this
I present messages as below - it works great, when there is an error it shows the error.
But, how do I retain these messages - so the user can see all of the errors they had since their session started?
I.e. if I working for a while, I might want to review all my form validation failures at the end.
Thanks
messages.error(request, ('ERROR: Your milestone was not created. Milestone points must be numeric. Please try again.'))
Maybe Django's Sessions.
https://docs.djangoproject.com/en/3.2/topics/http/sessions/#using-sessions-in-views
def message_error(request, msg):
errors = request.session.get('errors', []):
errors.append(msg)
request.session['errors'] = errors
messages.error(request, msg)
def get_message_error_list(request):
return request.session.get('errors', [])

Django REST Framework User Registration Privacy Issue: Looking for best practice to not show exception if user with given email exists already

I am suprised I have not found anything on the web regarding the following issue which I thought should be common? I may just have used the wrong search terms so I am happy to receive links with more info.
My problem is that when using ACCOUNT_EMAIL_VERIFICATION = 'mandatory' I want to not give a clue to any user except the owner of an email address whether that mail is registered on my website, i. e. not show a "A user is already registered with this e-mail address." if an existing email is entered.
My assumption is that that would require for the registration endpoint to return the same response independent of whether the email exists or not. I see several possible approaches, but none seems to be a good one:
Use a custom exception handler to remove the exception in question from the error messages sent. That means I have to somehow identify the abovementioned exception among all error messages sent so I can still keep the others in the response. I guess I have to identify the exception message by a string the actual error message (possibly dependent on language settings?). If there are multiple error messages I can simply remove the one in question. But if the exception is the only exception I'd have to fake the same response that would be given after successful creation of a user. That sounds fiddly and not robust to me.
Check uniqueness before is_valid() is called and fake a successful response. But then I won't be able to return exceptions from possible additions errors.
Remove the unique-contraint from the DB so that the is_valid() method does not raise an error and prevent the instance from saving in perform_create(). But I don't really want to remove that database-level protection layer.
There must be a better solution out there I hope?
Appreciate any help!
Mike
You should change the error/validation message to be more generic, for example:
Email address error.
It is very similar situation as in login. You don't write explicitly that the email address doesn't exist or the password is too short, you just send a message:
Invalid email or password.
Additionally, you can add information that if the problem repeats please contact to system/service administrator. Then if a person contacts the administrator from his/her email problem can be fixed manually.
Solution with generating unique email address might be too complex and can bring unexpected problems. What if the user just forgot about the old account and recreate the new account, with loss of previous account's data.
I came up with a possible solution today. Seems to be a little improvised but avoids the abovementioned problems. Would be happy to hear your thoughts or suggestions.
The approach was to override the create method and if it fails to the uniqueness constraint generate a pseudo serializer instance with a modified email address that would be unique and only return errors from that pseudo instance.
Additionally, the entire create request only returns an HTTP 200 OK status, independent of whether a user was created or if a user with that mail existed.
#custom create method to not return "email exists" error to user if email exists
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try: #try if data is valid. If so, create user.
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
print('User created:')
print(user)
except ValidationError as err: #if Validation error was returned
email = request.data['email']
email = get_adapter().clean_email(email)
# check if one issue was due to existing email
if allauth_settings.UNIQUE_EMAIL and email and email_address_exists(email):
# generate unique version of the mail address by appending string to local part to revalidate with unique mail address
provisional_email = make_email_unique(email, uniqueness_validator=email_address_exists)
# create fake request.data dict with unique mail address
provisional_request_data = request.data.copy()
provisional_request_data['email'] = provisional_email
#get new serializer with new request.data
provisional_serializer = self.get_serializer(data=provisional_request_data)
#check if data was valid if email was unique
try:
provisional_serializer.is_valid(raise_exception=True)
# get user who owns original email from request
user = get_user_from_email(request.data['email'])
except ValidationError as err2:
# else validate again, this time return any errors that may occur even with unique email
raise err2
else: #if error was not (also) due to existing mail problem raise validation errrors
raise err
headers = self.get_success_headers(serializer.data)
# always return 200 OK if no error is shown (i. e. no 201 CREATED that could hint to an existing mail address)
return Response(status=status.HTTP_200_OK,
headers=headers)

Django: exceptions and returns, what is the correct way to handle multiple potential situations?

I have a function in a Manager which activates a user account via a key. When a key is provided, several checks need to be performed: does the key exist? has the key expired? and then if not, the manager activates the account.
def activate(key):
try:
profile = self.get(key=key)
except self.model.DoesNotExist:
return None
if not profile.key_expired():
## Activate user
return user
return None
The issue is of course, that this returns False for both 'the key doesn't exist', and 'the key given is expired'. Giving False for both doesn't tell my upstream view what was the issue at hand. I don't do a 404 error as that is opaque to the user and doesn't help matters.
What is the best/correct django/pythonic way of handling this to give more useful information upstream? Are custom errors the way forward? Should I return values for analysis upstream (seems an ugly solution)? Is there another way?
I'd raise an exception inside activate and catch it outside.
def activate(key):
try:
profile = self.get_query_set().get(key=key)
except self.model.DoesNotExist:
raise
if profile.key_expired():
raise YourCustomException()
# or simply return False
## Activate user
return user
Also I'd suggest to use self.get_query_set().get(key=key) instead of self.get(key=key)

Control a function (E.g send mail) from a users profile page

I have a profile page like so: http://i.stack.imgur.com/Rx4kg.png . In management I would like a option "Notify by mail" that would control my send_email functions in every application I want. As example I'm using django-messages and it sends private messages aswell as emails when you send a message. I would like for the user to be able to specify if he wants emails aswell when he gets a message.
messages/utils.py
def new_message_email(sender, instance, signal,
subject_prefix=_(u'New Message: %(subject)s'),
template_name="messages/new_message.html",
default_protocol=None,
*args, **kwargs):
"""
This function sends an email and is called via Django's signal framework.
Optional arguments:
``template_name``: the template to use
``subject_prefix``: prefix for the email subject.
``default_protocol``: default protocol in site URL passed to template
"""
if default_protocol is None:
default_protocol = getattr(settings, 'DEFAULT_HTTP_PROTOCOL', 'http')
if 'created' in kwargs and kwargs['created']:
try:
current_domain = Site.objects.get_current().domain
subject = subject_prefix % {'subject': instance.subject}
message = render_to_string(template_name, {
'site_url': '%s://%s' % (default_protocol, current_domain),
'message': instance,
})
if instance.recipient.email != "":
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
[instance.recipient.email,])
except Exception, e:
#print e
pass #fail silently
Apparently instance.recipient.email is the email for the recipient user. So my questions are: How do I go about creating an option in my profile management that can be used in my new_message_email to check if the user wants emails or not? My own thoughts are that I need to save a value in the database for the user and then check for that value in new_message_email function. How I do that isn't clear though. Do I create a new function in my userprofile/views.py and class in userprofile/forms.py? And have my userprofile/overview.html template change them? Some specifics and thoughts if this is the right approach would help alot!
You probably want to start off by creating a user profile so that you have a good way to store weather or not the user wants these emails sent to them. This is done using the AUTH_PROFILE_MODULE setting in your settings.py.
Once you have the data stored, you should be able to access it from instance.recipient (assuming that instance.recipient is a User object). So you could change your code to:
if instance.recipient.get_profile().wants_emails and instance.recipient.email != "":
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL,
[instance.recipient.email,])
Done and done.

Proper technique for a contact form?

I have a contact form at shantiyoga.ca/contact
With the view
def contact(request):
template = 'contact.html'
form = ContactForm(request.POST or None)
if form.is_valid(): # All validation rules pass
subject = form.cleaned_data['subject']
message = "%s\n\n%s" % (form.cleaned_data['message'], form.cleaned_data['sender'])
cc_myself = form.cleaned_data['cc_myself']
recipients = ['contact#shantiyoga.ca']
sender = form.cleaned_data['sender']
if cc_myself:
recipients.append(sender)
headers = {'Reply-To': form.cleaned_data['sender']}
from django.core.mail import send_mail
send_mail(subject,message,sender,recipients,headers)
return redirect('/thanks/')
return render_to_response(template, {'form': form, 'current':'contact'}, context_instance=RequestContext(request))
Which works fairly well. I'm not terribly sophisticated with Django and my Python skills are not quite up to snuff, so please bear with me if I step through this in a basic fashion.
I would like to clarify that there is no way for the form recipient (contact#shantiyoga.ca) to receive the contact form from the value of the email field (user entered). It will always be sent by the authenticated email in my settings.py, which at this point is my personal email?
A user fills out the contact form and hits submit, an email is sent to contact#shantiyoga.ca from my personal email, and if the user decides to cc themself, a copy of the email is sent to them, also from my personal email.
This is not ideal, should I create an email like contactform#shantiyoga.ca for my settings.py to send the email from?
Also, the headers = {'Reply-To': form.cleaned_data['sender']} does not appear to be doing anything and I can't seem to find documentation describing its proper usage, has anyone had success using this technique?
Thank you for your time,
Noah
I would like to clarify that there is no way for the form recipient (contact#shantiyoga.ca) to receive the contact form from the value of the email field (user entered). It will always be sent by the authenticated email in my settings.py, which at this point is my personal email?
You're setting the sender of the email to sender but using EMAIL_HOST, correct? If this is the case, be careful that your SMTP account will let you send email from users other than your domain. Normally, providing you have an authenticated account on that server, you'll be able to set the From: field to whatever you like.
So in short, this email will hit your inbox appearing to be from the sender variable. So when you hit reply, you'll be able to email them back, which is I think what you want. However, it will be sent using the SMTP server whose authentication details you provide. There is a disconnect between being able to send email (SMTP) and being able to receive it, which I think has got you confused.
I've never tried it, but to add extra headers I believe you need to use the EmailMessage object. According to the docs, you would:
e = EmailMessage(headers={'Reply-To':...
e.send()
Be careful doing this; specifically, be careful to strip out newlines in the reply to field. I do not know if the default clean methods would do this.
Finally, there isn't much wrong with your django/python at all. The only thing I'd say, by way of awareness, is not to use this:
return redirect('/thanks/')
but instead:
return HttpResponseRedirect(reverse('myapp.views.method',args=tuple,kwargs=dict))
This gives you the ability to not hardcode urls into your application. The reverse method will look them up from urls.py for you, so you can move your application around/change urls and it will still work.
Here is signature of send_mail from django documentation:
send_mail(subject, message, from_email, recipient_list, fail_silently=False, auth_user=None, auth_password=None, connection=None)
As you can see it does not have headers argument.
You will need to use EmailMessage object directly.
It would also be nice to remove email-formatting from view. I would write everything something more like this:
from django.core.mail.message import EmailMessage
from django.conf import settings
class ContactsEmailMessage(EmailMessage):
def __init__(self, sender, subject, message, cc_myself):
super(ContactEmailMessage, self).__init__(
subject=subject,
body=self._get_message(self, sender, message),
from=settings.DEFAULT_FROM_EMAIL,
to=settings.CONTACT_RECIPIENTS,
headers=self._get_headers(sender),
cc=(sender, ) if cc_myself else None
)
def _format_message(self, sender, message):
return "%s\n\n%s" % (sender, message)
def _get_headers(self, sender):
return {
'reply-to': sender
}
Now you can write clean and easy to read view:
from myproject.mail.message import ContactsEmailMessage, BadHeaderError
from django.core.exceptions import SuspiciousOperation
def contact(request):
...
form = ContactForm(request.POST or None)
if form.is_valid(): # All validation rules pass
try:
message = ContactsEmailMessage(**form.cleaned_data)
message.send()
except BadHeaderError:
# django will raise this error if user will try to pass suspicious new-line
# characters to "sender" or other fields. This is safe-guard from
# header injections
raise SuspiciousOperation()
return redirect('/thanks/')
...