I have some peculiar requirements, and I'm trying to figure out the best approach to code it. Best being efficiency foremost and maintainability second-most. The requirement goes something like this (bear with me):
I have a feature on my Django website that's meant to serve both registered and unregistered users. This feature is going to be gated behind a pin code for each user. I require these pins to be randomly generated (and not serially).
The pin code will need to be memorable - which means pin len becomes a factor. I'm going for a 4 digit pin hence. And numbers only, since alphanumeric is less memorable than purely digits (according to usability tests I've conducted - I'm sticking to these results).
I can't have collisions among pin codes since they'll be used as identifiers too - so all of them must be unique. My range will lie between 0000 to 9999. Only 10K unique combinations will be possible. This is restrictive, but memorability of the pin code is a higher priority than the size of the pool of possible pin codes. So I'll sacrifice.
Lastly, pin codes for registered users will be permanently assigned. Unregistered users, on the other hand, will be assigned pins that remain booked for no more than 24 hrs, after which they expire - and hence enter the pool of unused pins once more.
Imagine my data model for the above (in models.py) is like so:
class Inbox(models.Model):
pin_code = models.CharField(default='0')
owner = models.ForeignKey(User)
creation_time = models.DateTimeField(auto_now_add=True)
In views.py, I'll need a way to allot an available pin_code to each Inbox object I create. What's the most efficient logic? Here's how I'm thinking I can approach it:
def expire_pin(time_difference=None):
#admin user (with id 1) is assumed as the 'unregistered user'
Inbox.objects.filter(creation_time__lte=time_difference,owner_id=1).update(pin_code='0')
def get_pin():
parent_list = ['{:04d}'.format(i) for i in range(10000)]
day_ago = timezone.now() - timedelta(hours=24)
expire_pin(day_ago)
to_exclude = Inbox.objects.filter(~Q(pin_code='0')).values_list('pin_code',flat=True)
new_list = [item for item in parent_list if item not in to_exclude]
return random.choice(new_list)
Optional: you don't have to read it, but here is what I'm using the pin_code for. Every user - registered and unregistered - is allotted an inbox that is accessible at example.com/pin/XXXX (XXXX being the pin_code). Users can share this inbox address with their friends over social media. Friends can then log into it via their mobile number, viewing content the former user left particularly for the eyes of the said friend. Catch the drift?
I need this feature for both registered and unregistered users - thus the need to allot a pin_code even to unknown users. But I want to be able to recycle unregistered user codes, so that I don't run out of the 10K possibilities too fast. I have a moderately big user base on this website.
Ultimately though, I will run out of 10K combinations. I'm thinking I'll write code that seamlessly shifts over to 5-digit pins thereafter. But even in that scenario, if any 4-digit pins expire and become available, they'll get first priority during allotment. If you can help me with that part as well, great!