Is there a way to set two variables as unique together? - python

I'm creating an application that i have username and nickname for the same person:
username = models.CharField(
max_length=40, unique=True, db_index=True)
nickname = models.CharField(max_length=100, unique=True)
But i need to define this two variables as unique like:
If an user A has:
username: bird
nickname: dog
and an user B try to make registration like:
username: dog
nickname: butterfly
I will not accept because dog is already taken. Or if an user try to put nickname as same as username.
I'm thinking to create a registration view to make this validation manually. But is there a way to make this in the declaration?

You can use the unique_together setting under Meta.
eg.
class Meta:
unique_together = [[field1, field2]]
In your case this would mean that bird/dog would be unique, as would bird/butterfly. dod/bird would be allowed as the fields are different from bird/dog, but a second bird/dog would not be allowed.

SQL isn't generally made to enforce uniqueness across two columns, so the only solution I'm aware of would be to split this up into two tables, one for typed names, and one for all other user information. You'd adapt that answer to models with something like:
class User(models.Model):
# Define fields aside from username and nickname here
class Name(models.Model):
user = models.ForeignKey('Users', on_delete=models.CASCADE)
USERNAME = 'U'
NICKNAME = 'N'
TYPE_CHOICES = (
(USERNAME, 'username'),
(NICKNAME, 'nickname')
)
type = models.CharField(max_length=1, choices=TYPE_CHOICES)
name = models.CharField(max_length=100, unique=True)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'type'], name='unique_user_type'),
]
# Alternative more concise, but semi-deprecated, approach:
# unique_together = [['user', 'type']]
By doing this, you use a single shared column for all names; it's got a unique constraint, so no name can appear twice as either username or nickname. Similarly, the combined uniqueness constraint on the user and type fields means any given user can only have one of each type of name, user or nick, while the foreign key relationship means you can access username/nickname data through the User model, and the on_delete=models.CASCADE setting means that if the associated user is deleted, then so are the associated names.
Main downsides are:
This doesn't force you to create either a username or nickname for a given user; your program logic would have to ensure that a user is never (permanently) created unless both username and nickname are valid
Item #1 implies that you must explicitly operate transactionally, as opposed to relying on per-table insertion atomicity; when creating a new user, you'd need to begin a transaction, create the user, flush to get a valid user id, then create both names using the new user id, rolling back the whole transaction if any constraints are violated as you go
Inability to define different limits for username vs. nickname; since they share a field, you can't impose different length limits on each field (your wrapper code could do so, but the DB couldn't attempt to save space based on the different limits, even if you used complicated CheckConstraints to impose independent limits)
All that said, I'd discourage this approach. If nicknames aren't security-relevant, you can allow them to overlap (StackOverflow allows this; the link to the user profile is how you tell folks apart); they don't really need to be unique at all. Usernames obviously need to be unique, but there is no need to prevent a nickname from overlapping a username if the nickname is purely for display.

Related

How to see all possible query sets of a model in django

I have two models like below in django
class User(AbstractBaseUser, PermissionsMixin):
first_name = models.CharField(....)
last_name = models.CharField(_(....)
email = models.EmailField(...)
class VcsToken(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
as you can see VcsToken is related to User, and a user can have many VcsToken,
So how do I get all the VcsTokens of a User.
Similarly I have many one to many relationships from user to other models, so How do I know their reference name? (I know its a set but how do I know the set name? ) Is there any way to list the query set names for a model.
What you are looking for is the feature for walking backward on the foreign key relationships. This is covered here in the docs: https://docs.djangoproject.com/en/3.0/topics/db/queries/#following-relationships-backward
In your example you should be able to access VcsToken from User like this:
user = User.objects.get(pk=1) # let's get an example user
user.vcstoken_set.all() # returns all related VcsToken objects.
Optionally, when the ForeignKey is defined, you can specify a related_name argument that would be used by django for this purpose. For example:
class User(AbstractBaseUser, PermissionsMixin):
...
email = models.EmailField(...)
class VcsToken(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='tokens')
If such an argument is specified, then that is the reverse lookup name used by django and you would need to do user.tokens.all() to access them. When no such argument is specified a default name is used by django that ends with "_set".
Hope this helps, let me know if anything needs to be clarified.
For a given User object myuser, you can access this with:
myuser.vcstoken_set.all()
so How do I know their reference name?
This is the value for the related_name=… parameter [Django-doc] if there is no such parameter in the ForeignKey construction, it will default to modelname_set, with the modelname in lowercase.
Is there any way to list the query set names for a model.
You can access all ManyToOneFields with:
django.db.models.fields.reverse_related.ManyToOneRel
[f.get_accessor_name() for f in User._meta.get_fields() if isinstance(f, ManyToOneRel)]
This will construct a list of the names of ForeignKeys in reverse.

(Django) How to make the User FK register the User name instead of a Code (int)

In my models.py, I have the request_author field defined as:
request_author = models.ForeignKey(
User,
on_delete = models.CASCADE
)
This makes the request_author field to be registered with a user code (int) number in the API database. However, I would like to have it as the user name. How do I do that?
You can use the to_field=... parameter [Django-doc], for that:
The field on the related object that the relation is to. By default, Django uses the primary key of the related object. If you reference a different field, that field must have unique=True.
So you can here set the to_field to 'username':
request_author = models.ForeignKey(
User,
on_delete = models.CASCADE,
to_field='username'
)
The username of the User model is unique=True, so this will not be a problem here. If you define a custom user model, than this does not per se holds.
Note that if you change the to_field, this might fail, since now it refers to a different field. In case you are just starting to build an application, it might be better to drop the database, remove old migrations, and then create a new migration file.

Django - Get objects that don't have one to to one attribute

I have a concept where there is a model that has a OneToOne field with User. How can I query for all users that are not assigned to one of these?
For example:
class SpecialUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
Not all users are special users. How can I get all users that are not special users using objects (ie: User.objects.filter(something)).
Thanks!
You can do the following:
User.objects.filter(specialuser=None)
Or:
User.objects.filter(specialuser__isnull=True)
You can provide related_name to make it more readable or to avoid pottential collisions. Example:
class SpecialUser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name="special_user_profile")
In this case, you'd use the related name instead:
User.objects.filter(special_user_profile=None)

Django - one to one relation, mostly NULLs at one end, NOT NULL on the other

I’m writing simple application with Django and PostgreSQL for managing a home book library, where I can have many borrower profiles for people who borrowed books (Borrower model). And I have users, who can borrow a book themselves, so that the book becomes borrowed by the user’s borrower profile.
On the other hand admin can lend books to any borrower, even to one unregistered as a user.
So I have a few MyUsers (and that field, btw, references Django’s User) and many Borrowers, and I want to create a one-to-one relation between them, but every MyUser has to reference one unique Borrower, but many Borrowers will not reference any existing MyUsers (they can only reference one or none, or in another words, be referenced by only one or no user).
My question is: how to model that optimally? Using models.OneToOneField, models.ForeignKey and which model should reference which?
I will probably have many borrowers, who do not have user accounts.
Natural solution seems to be OneToOneField(Borrower, null=False) in a User model. But then when searching for users based on borrowers I will have to mostly deal with DoesNotExists exceptions and only once in a while I will get a proper result.
I can also make ForeignKey(Borrower, unique=True, null=False) – then I will have to check sets having single element or empty.
And I can make ForeignKeys both ways:
class Borrower(models.Model):
# ...
user = models.ForeignKey(MyUser, unique=True, null=True)
class MyUser(models.Model):
# ...
borrower = models.ForeignKey(Borrower, unique=True, null=False)
That implicitly defines the relations and I can easily make searches both ways, but it also makes one additional, redundant field in database tables.
I will probably just stick with OneToOneField for now, but I’d like to know which approach makes most sense in this case. What are the proc and cons? And is there an alternative, better solution?
I would stick with the OneToOneField; as you say, it is the most natural solution.
The only downside you mentioned is that borrower.user can raise a DoesNotExist exception. If you don't like that, you can always define your own method (or property) to return something else (like None) instead. Something like:
class MyUser(models.Model):
borrower = models.OneToOneField(Borrower, null=False)
class Borrower(models.Model):
#property
def user(self):
try:
return self.myuser
except DoesNotExist:
return None
#user.setter
def user(self, user):
self.myuser = user

How to perform queries in Django following double-join relationships (or: How to get around Django's restrictions on ManyToMany "through" models?)

There must be a way to do this query through the ORM, but I'm not seeing it.
The Setup
Here's what I'm modelling: one Tenant can occupy multiple rooms and one User can own multiple rooms. So Rooms have an FK to Tenant and an FK to User. Rooms are also maintained by a (possibly distinct) User.
That is, I have these (simplified) models:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User)
maintainer = models.ForeignKey(User)
tenant = models.ForeignKey(Tenant)
The Problem
Given a Tenant, I want the Users owning a room which they occupy.
The relevant SQL query would be:
SELECT auth_user.id, ...
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id;
Getting any individual value off the related User objects can be done with, for example, my_tenant.rooms.values_list('owner__email', flat=True), but getting a full queryset of Users is tripping me up.
Normally one way to solve it would be to set up a ManyToMany field on my Tenant model pointing at User with TenantRoom as the 'through' model. That won't work in this case, though, because the TenantRoom model has a second (unrelated) ForeignKey to User(see "restictions"). Plus it seems like needless clutter on the Tenant model.
Doing my_tenant.rooms.values_list('user', flat=True) gets me close, but returns a ValuesListQuerySet of user IDs rather than a queryset of the actual User objects.
The Question
So: is there a way to get a queryset of the actual model instances, through the ORM, using just one query?
Edit
If there is, in fact, no way to do this directly in one query through the ORM, what is the best (some combination of most performant, most idiomatic, most readable, etc.) way to accomplish what I'm looking for? Here are the options I see:
Subselect
users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
Subselect through Python (see Performance considerations for reasoning behind this)
user_ids = id__in=my_tenant.rooms.values_list('user')
users = User.objects.filter(id__in=list(user_ids))
Raw SQL:
User.objects.all("""SELECT auth_user.*
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id""")
Others...?
The proper way to do this is with related_name:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User, related_name='owns')
maintainer = models.ForeignKey(User, related_name='maintains')
tenant = models.ForeignKey(Tenant)
Then you can do this:
jrb = User.objects.create(username='jrb')
bill = User.objects.create(username='bill')
bob = models.Tenant.objects.create(name="Bob")
models.Room.objects.create(owner=jrb, maintainer=bill, tenant=bob)
User.objects.filter(owns__tenant=bob)

Categories

Resources