21👍
If you don’t expect to get a large response rate, then you should try to store all of the data in the URL itself. This way, you don’t need to store anything in the database, and will have data storage proportional to the responses rather than the emails sent.
Updated: Let’s say you had two strings that were unique for each user. You can pack them and unpack them with a protecting hash like this:
import hashlib, zlib
import cPickle as pickle
import urllib
my_secret = "michnorts"
def encode_data(data):
"""Turn `data` into a hash and an encoded string, suitable for use with `decode_data`."""
text = zlib.compress(pickle.dumps(data, 0)).encode('base64').replace('\n', '')
m = hashlib.md5(my_secret + text).hexdigest()[:12]
return m, text
def decode_data(hash, enc):
"""The inverse of `encode_data`."""
text = urllib.unquote(enc)
m = hashlib.md5(my_secret + text).hexdigest()[:12]
if m != hash:
raise Exception("Bad hash!")
data = pickle.loads(zlib.decompress(text.decode('base64')))
return data
hash, enc = encode_data(['Hello', 'Goodbye'])
print hash, enc
print decode_data(hash, enc)
This produces:
849e77ae1b3c eJzTyCkw5ApW90jNyclX5yow4koMVnfPz09JqkwFco25EvUAqXwJnA==
['Hello', 'Goodbye']
In your email, include a URL that has both the hash and enc values (properly url-quoted). In your view function, use those two values with decode_data to retrieve the original data.
The zlib.compress may not be that helpful, depending on your data, you can experiment to see what works best for you.
5👍
You could set this up with URLs like:
http://yoursite.com/temp/1a5h21j32
Your URLconf would look something like this:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^temp/(?P<hash>\w+)/$', 'yoursite.views.tempurl'),
)
…where tempurl is a view handler that fetches the appropriate page based on the hash. Or, sends a 404 if the page is expired.
- [Django]-Can I use an existing user as Django admin when enabling admin for the first time?
- [Django]-IntegrityError duplicate key value violates unique constraint – django/postgres
- [Django]-Django.core.exceptions.ImproperlyConfigured: Error loading psycopg module: No module named psycopg
5👍
models
class TempUrl(models.Model):
url_hash = models.CharField("Url", blank=False, max_length=32, unique=True)
expires = models.DateTimeField("Expires")
views
def generate_url(request):
# do actions that result creating the object and mailing it
def load_url(request, hash):
url = get_object_or_404(TempUrl, url_hash=hash, expires__gte=datetime.now())
data = get_some_data_or_whatever()
return render_to_response('some_template.html', {'data':data},
context_instance=RequestContext(request))
urls
urlpatterns = patterns('', url(r'^temp/(?P<hash>\w+)/$', 'your.views.load_url', name="url"),)
//of course you need some imports and templates
- [Django]-Class Based Views VS Function Based Views
- [Django]-Django Static Files Development
- [Django]-How to update fields in a model without creating a new record in django?
1👍
It depends on what you want to do – one-shot things like account activation or allowing a file to be downloaded could be done with a view which looks up a hash, checks a timestamp and performs an action or provides a file.
More complex stuff such as providing arbitrary data would also require the model containing some reference to that data so that you can decide what to send back. Finally, allowing access to multiple pages would probably involve setting something in the user’s session and then using that to determine what they can see, followed by a redirect.
If you could provide more detail about what you’re trying to do and how well you know Django, I can make a more specific reply.
- [Django]-AngularJS with Django – Conflicting template tags
- [Django]-Check for presence in a list django template
- [Django]-How do I keep my Django server running even after I close my ssh session?
1👍
I think the solution lies within a combination of all the suggested solutions. I’d suggest using an expiring session so the link will expire within the time period you specify in the model. Combined with a redirect and middleware to check if a session attribute exists and the requested url requires it you can create somewhat secure parts of your site that can have nicer URLs that reference permanent parts of the site. I use this for demonstrating design/features for a limited time. This works to prevent forwarding… I don’t do it but you could remove the temp url after first click so only the session attribute will provide access thus more effectively limiting to one user. I personally don’t mind if the temp url gets forwarded knowing it will only last for a certain amount of time. Works well in a modified form for tracking invited visits as well.
- [Django]-How django time zone works with model.field's auto_now_add
- [Django]-What is the difference render() and redirect() in Django?
- [Django]-Sortable table columns in django
0👍
It might be overkill, but you could use a uuidfield on your model and set up a Celerybeat task to change the uuid at any time interval you choose.
If celery is too much and it might be, you could just store the time the URL is first sent, use the timedelta function whenever it is sent thereafter, and if the elapsed time is greater than what you want just use a redirect. I think the second solution is very straightforward and it would extend easily. It would be a matter of having a model with the URL, time first sent, time most recently sent, a disabled flag, and a Delta that you find acceptable for the URL to live.
- [Django]-Passing STATIC_URL to file javascript with django
- [Django]-How to force a user logout in Django?
- [Django]-Python – Cleanest way to override __init__ where an optional kwarg must be used after the super() call?
0👍
A temporary url can also be created by combining the ideas from @ned-batchelder’s answer and @matt-howell’s answer with Django’s signing
module.
The signing
module provides a convenient way to encode data in the url, if necessary, and to check for link expiration. This way we don’t need to touch the database or session/cache.
Here’s a minimal example with an index page and a temp page:
The index page has a link to a temporary url, with the specified expiration. If you try to follow the link after expiration, you’ll get a status 400 "Bad Request" (or you’ll see the SuspiciousOperation
error, if DEBUG
is True
).
urls.py
...
urlpatterns = [
path('', views.index, name='index'),
path('<str:signed_data>/', views.temp, name='temp'),
]
views.py
from django.core import signing
from django.core.exceptions import SuspiciousOperation
from django.http import HttpResponse
from django.urls import reverse
MAX_AGE_SECONDS = 20 # short expiration, for illustrative purposes
def generate_temp_url(data=None):
# signing.dumps() returns a "URL-safe, signed base64 compressed JSON string"
# with a timestamp
return reverse('temp', args=[signing.dumps(data)])
def index(request):
# just a convenient usage example
return HttpResponse(f'<a href="{generate_temp_url()}">temporary link</a>')
def temp(request, signed_data):
try:
# load data and check expiration
data = signing.loads(signed_data, max_age=MAX_AGE_SECONDS)
except signing.BadSignature:
# triggers an HttpResponseBadRequest (status 400) when DEBUG is False
raise SuspiciousOperation('invalid signature')
# success
return HttpResponse(f'Here\'s your data: {data}')
Some notes:
-
The responses in the example are very rudimentary, and only for illustrative purposes.
-
Raising a
SuspiciousOperation
is convenient, but you could e.g. return anHttpResponseNotFound
(status 404) instead. -
The
generate_temp_url()
returns a relative path. If you need an absolute url, you can do something like:temp_url = request.build_absolute_uri(generate_temp_url())
-
If you’re worried about leaking the signed data, have a look at alternatives such as Django’s password reset implementation.
- [Django]-How to fix function/symbol 'pango_context_set_round_glyph_positions' error
- [Django]-Passing STATIC_URL to file javascript with django
- [Django]-Replace textarea with rich text editor in Django Admin?