Creating a 21 character unique field for a model in Django - python

I know the id is unique by itself, I want to create a unique field thats longer and less easy to guess. In short, this field will be a unique, 21 character alphanumeric string. I am doing this using this code below:
class Organization(models.Model):
name = models.CharField(max_length=255)
key = models.CharField(max_length=21, unique=True, blank=True, null=True)
def save(self, *args, **kwargs):
if self.key == '' or self.key == None:
key = ''
for i in range(21):
key += random.choice(string.lowercase + string.uppercase + string.digits)
if Organization.objects.filter(key=key).exists():
self.save() # Try again
else:
self.key = key
super(Organization, self).save(*args, **kwargs)
In the save method, I do the following things:
See if the object's key exists or not.
If not, I generate a 21 character key.
Then I check if an Organization with the key I generated exists or not.
If not, then the object's key is set to the generated one and saved.
Is this the right way to do this?
Is there a better way with less code perhaps? Also, notice how my key field is unique, but also can be blank. Can that be an issue? So far, this has worked pretty well. Any help is appreciated.

Right now it appears that you are trying to emulate a UUID (universal unique identifier) using a CharField. While this approach probably won't cause too many issues (it does offer, after all, (26 + 26 + 10)**21 possible combinations), it is a pretty naive approach.
Why not use Django's native UUIDField? Here is an example:
class Organization(models.Model):
name = models.CharField(max_length=255)
key = models.UUIDField(default=uuid.uuid4, editable=False)
This will use the uuid Python module. It will generate a random UUID:
In [1]: uuid.uuid4()
Out[1]: UUID('3be0e5db-eb0a-4f09-bda3-37006760ab38')
In [2]: uuid.uuid4()
Out[2]: UUID('8d916804-0a53-478e-8e4f-ebda5d7a69ab')
Obviously, these UUID's are hexadecimal and contain hyphens, but I think it's preferable to follow this convention rather than to define your own unique identifier.

You can uuid4. But personally I prefer just CharField and assign the uuid4 characters to default. The reason for this is UUIDField will give lots of inconveniences while querying DB.
If you want to add a row (by insert raw DB command) after generating the UUID Code in a pythonic way, it will give you some problems if you try to iterate through DJANGO-QUERIED objects.
by assigning uuid code to default like below,
from django.db import models
import uuid
class Person(models.Model):
name = models.CharField(max_length=255)
code = models.CharField(max_length=8, unique=True, default=uuid.uuid4)

Related

Can I create a Django object using a subquery for a field value?

TLDR
When creating a new object using Django ORM, can I, in a transactionally safe / race-condition-free manner, set a field's value based on an already existing object's value, say F('sequence_number') + 1 where F('sequence_number') refers not to the current object (which does not exist yet) but to the most recent object with that prefix in the table?
Longer version
I have a model Issue with properties sequence_number and sequence_prefix. There is a unique constraint on (sequence_prefix, sequence_number) (e.g. DATA-1).
class Issue(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
sequence_prefix = models.CharField(blank=True, default="", max_length=32)
sequence_number = models.IntegerField(null=False)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["sequence_prefix", "sequence_number"], name="unique_sequence"
)
]
The idea is that issues —for auditing purposes— have unique sequence numbers for each variable (user-determined) prefix: when creating an issue the user selects a prefix, e.g. REVIEW or DATA, and the sequence number is the incremented value of the previous issue with that same sequence. So it's like an AutoField but dependent on the value of another field for its value. There can not be two issues DATA-1, but REVIEW-1 and DATA-1 and OTHER-1 all may exist at the same time.
How can I tell Django when creating an Issue, that it must find the most recent object for that given sequence_prefix, take the sequence_number + 1 and use that for the new object's sequence_number value, in a way that is safe of any race-condition?
A good way to archive this is to override the save() method of the Issue model.
For example:
class Issue(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
sequence_prefix = models.CharField(blank=True, default="", max_length=32)
sequence_number = models.IntegerField(null=False)
def save(self, *args, **kwargs):
max_id_by_prefix = Issue.objects.filter(sequence_prefix=self.sequence_prefix).max().id
self.sequence_number = max_id_by_prefix + 1
super(Issue, self).save(*args, **kwargs)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["sequence_prefix", "sequence_number"], name="unique_sequence"
)
]
In this way, before saving the object, you can take the max sequence_number of the sequence_prefix that you are saving.
Unless you want to use database sequences (AutoField), I believe you will need to implement something on your own. There are two options
Prevent concurrent inserts per specific sequence_prefix with some locking mechanism (I would use Redis for a distributed lock, to support multi-processing setup)
Implement your own sequencing (again, Redis is a perfect choices), which will provide you with auto-incrementing sequence_number per prefix. For example:
sequence_number = redis_client.incr('sequence:REVIEW')

How to declare a time-based alphanumerical id field with predefined aplphabetic part (Django model)

I am developing an app in Django.
I wanted to insert in my model an auto-incrementing alphanumerical unique ID field, having, by default, a fixed alphabetical part and an auto-incrementing numerical part. But I also want the availability to change, from admin section, this id to another alphanumerical one, with a different alphanumerical and numerical part.
I tryed to implement this, but it turned out that trying to implement such a field and making it the autofield of the model generates problems in my database.
So I am changing my aim: now I want to implement a time-based alphanumerical unique field with predefined aplphabetic part. Please note: I don't want to overwrite the django default id field, I just want to include in my model a field that gets as default value a unique customized alphanumerical value.
Here is what I did, in my models.py:
def return_timestamped_id():
prefix = "ITCH"
import time
this_time = time.time()
this_time = this_time *10000000
this_time = int(this_time)
timestamp = str(this_time)
default_value = prefix + timestamp
return(default_value)
class object_example(models.Model):
name = models.CharField(max_length=256, blank=True, null=True)
Id_generated = models.CharField(max_length=256, blank=False, null=False, unique=True, default=return_timestamped_id())
The problem is that, as I add objects to this model from the admin section, The
Id_generated is always the same.
I expected that the return_timestamped_id() function was called every time I add a new object. It is clear instead that is called just once and then the same return value is passed to the Id_generated of every new object.
How can I change my code in order to get a different timestamp every time a new object is added?
As you probably saw in the Django docs, you can use either a value or a callable as a default. If you use a callable (e.g. a function) then it will be called each time a default is needed.
The problem: you were passing a value because you were calling your function default=return_timestamped_id(). The function was being called once, when your module (models.py) was imported into the application.
The solution: pass the function itself default=return_timestamped_id
You can see in the django.models.Fields class the relevant code (comments mine):
class Field():
def __init__(self, ..., default=NOT_PROVIDED,...):
...
self.default = default # save the default as a member variable
...
def get_default(self):
"""Return the default value for this field."""
return self._get_default()
#cached_property
def _get_default(self):
if self.has_default():
if callable(self.default): # if it is callable, return it
return self.default
return lambda: self.default # else wrap in a callable

Django order by foreign key

I was programming a function in python that required a group of objects of a model to be ordered by a specific parameter (the codename of a problem), this parameter resulted to be a string. When I sorted it inside the Problems table, it worked correctly, however, when I sorted it inside ProblemUser model (using order_by) the sort was completely incorrect respecting the real results.
I managed to work it around by sorting the elements AFTER i got it from the model using sorted() function, however, i am still with the doubt on how to sort a foreign key in the order_by function.
class Problem(models.Model):
problem_name = models.CharField(max_length=256,default='Unnamed')
source = models.CharField(max_length=200)
url = models.CharField(max_length=200)
codename = models.CharField(max_length=200, primary_key=True)
difficulty = models.CharField(max_length=32,default='UNRATED')
class ProblemUser(models.Model):
problem = models.ForeignKey(Problem)
handler = models.ForeignKey(Handler)
solved = models.BooleanField(default=True)
voted = models.BooleanField(default=False)
entry = models.DateField()
t = ProblemUser.objects.filter(solved=1, handler=Handler.objects.get(id=1)).order_by('problem')
t[0].problem.codename < t[42].problem.codename
False
t[1].problem.codename < t[42].problem.codename
True
I also tried order_by('problem__codename')
Some codenames examples are (these are outputs that i remember to have seen when i ordered by the foreign key):
S_1_T
S_1000_E
S_1001_F
.
.
.
S_2_P
Thank you for your time and help! :).
try it like that please.
t = ProblemUser.objects.filter(solved=1, handler=Handler.objects.get(id=1)).order_by('problem__codename')

How to use unique_together method in django views

class Model1(models.Model):
username = models.CharField(max_length=100,null=False,blank=False,unique=True)
password = models.CharField(max_length=100,null=False,blank=False)
class Model2(models.Model):
name = models.ForeignKey(Model1, null=True)
unique_str = models.CharField(max_length=50,null=False,blank=False,unique=True)
city = models.CharField(max_length=100,null=False,blank=False)
class Meta:
unique_together = (('name', 'unique_str'),)
I've already filled 3 sample username-password in Model1 through django-admin page
In my views I'm getting this list as
userlist = Model1.objects.all()
#print userlist[0].username, userlist[0].password
for user in userlist:
#here I want to get or create model2 object by uniqueness defined in meta class.
#I mean unique_str can belong to multiple user so I'm making name and str together as a unique key but I dont know how to use it here with get_or_create method.
#right now (without using unique_together) I'm doing this (but I dont know if this by default include unique_together functionality )
a,b = Model2.objects.get_or_create(unique_str='f3h6y67')
a.name = user
a.city = "acity"
a.save()
What I think you're saying is that your logical key is a combination of name and unique_together, and that you what to use that as the basis for calls to get_or_create().
First, understand the unique_together creates a database constraint. There's no way to use it, and Django doesn't do anything special with this information.
Also, at this time Django cannot use composite natural primary keys, so your models by default will have an auto-incrementing integer primary key. But you can still use name and unique_str as a key.
Looking at your code, it seems you want to do this:
a, _ = Model2.objects.get_or_create(unique_str='f3h6y67',
name=user.username)
a.city = 'acity'
a.save()
On Django 1.7 you can use update_or_create():
a, _ = Model2.objects.update_or_create(unique_str='f3h6y67',
name=user.username,
defaults={'city': 'acity'})
In either case, the key point is that the keyword arguments to _or_create are used for looking up the object, and defaults is used to provide additional data in the case of a create or update. See the documentation.
In sum, to "use" the unique_together constraint you simply use the two fields together whenever you want to uniquely specify an instance.

Is there a way to simulate a foreach in Django using queries?

I have a model 'Status' with a ManyToManyField 'groups'. Each group has a ManyToManyField 'users'. I want to get all the users for a certain status. I know I can do a for loop on the groups and add all the users to a list. But the users in the groups can overlap so I have to check to see if the user is already in the group. Is there a more efficient way to do this using queries?
edit: The status has a list of groups. Each group has a list of users. I want to get the list of users from all the groups for one status.
Models
class Status(geomodels.Model):
class Meta:
ordering = ['-date']
def __unicode__(self):
username = self.user.user.username
return "{0} - {1}".format(username, self.text)
user = geomodels.ForeignKey(UserProfile, related_name='statuses')
date = geomodels.DateTimeField(auto_now=True, db_index=True)
groups = geomodels.ManyToManyField(Group, related_name='receivedStatuses', null=True, blank=True)
class Group(models.Model):
def __unicode__(self):
return self.name + " - " + self.user.user.username
name = models.CharField(max_length=64, db_index=True)
members = models.ManyToManyField(UserProfile, related_name='groupsIn')
user = models.ForeignKey(UserProfile, related_name='groups')
I ended up creating a list of the groups I was looking for and then querying all users that were in any of those groups. This should be pretty efficient as I'm only using one query.
statusGroups = []
for group in status.groups.all():
statusGroups.append(group)
users = UserProfile.objects.filter(groupsIn__in=statusGroups)
As you haven't posted your models, its a bit difficult to give you a django queryset answer, but you can solve your overlapping problem by adding your users to a set which doesn't allow duplicates. For example:
from collections import defaultdict
users_by_status = defaultdict(set)
for i in Status.objects.all():
for group in i.group_set.all():
users_by_status[i].add(group.user.pk)
Based on your posted model code, the query for a given status is:
UserProfile.objects.filter(groupsIn__receivedStatuses=some_status).distinct()
I'm not 100% sure that the distinct() call is necessary, but I seem to recall that you'd risk duplicates if a given UserProfile were in multiple groups that share the same status. The main point is that filtering on many-to-many relationships works using the usual underscore notation, if you use the names as defined either by related_name or the default related name.

Categories