Ensure User.username is unique even when User is deleted - django

I would like to know how, using Django, to ensure the uniqueness of a field value, even if objects are deleted.
For example, I create a CustomerUser with username = "carlos" , delete this user, then if I create another CustomerUser with username= "carlos" I will get an error.
models.py
class CustomUser(models.Model):
username = models.CharField(max_length=100 , unique=True)
shell
user_one = CustomUser.objects.create(username="carlos")
user_one.delete()
user_two = CustomUser.objects.create(username="carlos") ---> This should not be possible.
Should I save in another model all the usernames created or there is a Django function that assures the uniqueness of the username will be always True even after object deletion?

You can try this:
class CustomUser(models.Model):
username = models.CharField(max_length=100 , unique=True)
is_deleted = models.BooleanField(default=False)
Then just update the is_delete to True and filter user according that.
user_one = CustomUser.objects.create(username="carlos")
user_one.is_delete = True
user_one.save()
During filter you do something like this:
user_one = CustomUser.objects.filter(is_delete=False)

You can make use of a package like django-soft-delete. This package will not remove the record, but add an extra BooleanField named is_deleted that is set to True in case the object is removed. But the record itself still exists.
The package will thus override the .delete() method of the model object, and also use record managers that will for example count the number of records with is_deleted set to False, at the same time the database will still enforce that no two records with the same name will exist.

Related

How to allow a form to submit, but prevent blank fields from being saved using Django Admin 3.x?

I am using Django Admin, and have a model like this:
class Item(models.Model):
id = models.CharField(max_length=14, primary_key=True)
otherId = models.CharField(max_length=2084, blank=True)
I want id to be required and unique, and I want otherId to be optional on the Admin form, but if otherId is provided, it has to be unique.
The problem I am running into is, whenever I create an instance of Item using the Admin form and I do not provide an otherId, Django tries to save the otherId field as a blank value, but this means the second time I try to save an instance with a blank otherId value it violates the column's unique constraint and fails.
I need Django to check if the otherId field is falsey before saving, and if it is falsey, do not save that empty value along with the model. Is this possible?
You should add unique=True to otherId field.
otherid = models.CharField(max_length=2084, blank=True, null=True, unique=True)
Django ignore unique or not if otherId is blank.
I failed to understand the question very well but i think you need to override the save method of the django model and provide custom logic you stated above.
class Item(models.Model):
id = models.CharField(max_length=14, primary_key=True)
otherId = models.CharField(max_length=2084, blank=True)
def save(self, *args, **kwargs):
# handle you logic here
# check if self.id is empty string and do something about it
super(Item, self).save(*args, **kwargs)
For every model django also auto create a field id for primary key which is auto generated and incremented.
For disabling submission of blank field you must make the null and blank property False. Check the code.
Also note that the id field is automatically added in django so you need not mention that.
class Item(models.Model):
otherId = models.CharField(max_length=2084, blank=False, null=False)

Force model fields in Django

When creating a model in Django like this example:
class Musician(models.Model):
first_name = models.CharField(max_length=50, primary_key=True)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
I noticed some problem (not sure if that's best word) with this approach. There is nothing preventing you from creating something like:
musician = Musician()
musician.save()
effectively having primary_key value equal to None. I would like to force user to set first_name, but frankly speaking I cannot find any simple solution for that.
Is there a way to achieve this?
First of all, don't set first_name as primary key. Just leave the default primary key as the id field. A primary key needs to be unique (a first_name isn't) and should not be something a user enters.
Second, it's true that you cannot enforce a CharField to not be empty at the database level. But you can enforce it at the code level, so that anytime you create a Django Form and validate it, it will raise an error.
In fact, Django does it automatically for you, in your case. By default first_name is a required field, since you didn't set blank=True.
So if you do:
musician = Musician()
musician.full_clean()
this raises a ValidationError.
If you create a form for your model (which is what you need if you want users to create a Musician):
class MusicianForm(ModelForm):
class Meta:
model = Musician
fields = '__all__'
form = MusicianForm(data={})
form.instance.first_name
# ''
form.is_valid()
# False
form.save()
# ValueError: The Musician could not be created because the data didn't validate.
You'll also see that if you register Musician in admin.py for django admin site, you can't leave any of the fields empty. It just won't save.

FactoryBoy / Django - OneToOneField duplicate key error

I am writing tests for a large Django application with multiple apps. As part of this process I am gradually creating factories for all models of the different apps within the Django project.
However, I've run into some confusing behavior with FactoryBoy
Our app uses Profiles which are linked to the default auth.models.User model with a OneToOneField
class Profile(models.Model):
user = models.OneToOneField(User)
birth_date = models.DateField(
verbose_name=_("Date of Birth"), null=True, blank=True)
( ... )
I created the following factories for both models:
#factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
user = factory.SubFactory('yuza.factories.UserFactory')
birth_date = factory.Faker('date_of_birth')
street = factory.Faker('street_name')
house_number = factory.Faker('building_number')
city = factory.Faker('city')
country = factory.Faker('country')
avatar_file = factory.django.ImageField(color='blue')
tenant = factory.SubFactory(TenantFactory)
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
username = factory.Faker('user_name')
first_name = factory.Faker('first_name')
last_name = factory.Faker('last_name')
email = factory.Faker('email')
is_staff = False
is_superuser = False
is_active = True
last_login = factory.LazyFunction(timezone.now)
profile = factory.RelatedFactory(ProfileFactory, 'user')
Which I then run the followings tests for:
class TestUser(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
user = UserFactory()
self.assertTrue(user)
self.assertTrue(user.profile)
self.assertTrue(user.profile.tenant)
class TestProfile(TestCase):
def test_init(self):
""" Verify that the factory is able to initialize """
profile = ProfileFactory()
self.assertTrue(profile)
All tests in TestUser pass, but the TestProfile fails on the factory initialization ( profile = ProfileFactory()) and raises the following error:
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
Its not clear to me why a duplicate User would already exist, (there should only be one call to create one right?, especially since any interfering signals have been disabled)
My code was based on the example from the FactoryBoy documentation, which also dealt with Users / Profiles that are connected via a OneToOneKey
Does anyone know what I am doing wrong?
Update
As per the suggestions of both Bruno and ivissani I've changed the user line in the ProfileFactory to
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
Now all the tests described above pass successfully!
However I still run into the following issue - when other factories call the UserFactory the
IntegrityError: duplicate key value violates unique constraint "yuza_profile_user_id_key"
DETAIL: Key (user_id)=(1) already exists.
still returns.
I've included an example of a factory calling the UserFactory below, buts its happening to every factory that has a user field.
class InvoiceFactory(factory.django.DjangoModelFactory):
class Meta:
model = Invoice
user = factory.SubFactory(UserFactory)
invoice_id = None
title = factory.Faker('catch_phrase')
price_paid = factory.LazyFunction(lambda: Decimal(0))
tax_rate = factory.LazyFunction(lambda: Decimal(1.21))
invoice_datetime = factory.LazyFunction(timezone.now)
Changing the user field on the InvoiceFactory to
user = factory.SubFactory(UserFactory, profile=None)
Helps it pass some of the tests, but eventually runs into trouble since it no longer has a profile associated with it.
Weirdly the following (declaring the user before the factory) DOES work:
self.user = UserFactory()
invoice_factory = InvoiceFactory(user=self.user)
Its not clear to me why I still keep running into the IntegrityError here, calling the UserFactory() now works fine.
I think it's because your ProfileFactory creates a User instance, using the UserFactory which itself tries to create a new Profile instance using the ProfileFactory.
You need to break this cycle, as described in the documentation you link to:
# We pass in profile=None to prevent UserFactory from
# creating another profile (this disables the RelatedFactory)
user = factory.SubFactory('yuza.factories.UserFactory', profile=None)
If this doesn't work for you and you need more advanced handling, then I suggest implementing a post_generation hook where you can do more advanced things.
EDIT:
Another option is to tell Factory Boy to not recreate a Profile if there is already one for the User by using the django_get_or_create option:
#factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = profile_models.Profile
django_get_or_create = ('user',)
If you do so, you might be able to remove the profile=None that I suggested before.
EDIT 2:
This might also help, change the UserFactory.profile using a post_generation hook:
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
...
# Change profile to a post_generation hook
#factory.post_generation
def profile(self, create, extracted):
if not create:
return
if extracted is None:
ProfileFactory(user=self)
EDIT 3
I've just realised that the username field in your UserFactory is different from the one in factroy boy's documentation, and it's unique in Django. I wonder if this doesn't cause some old instances to be reused because the username is the same.
You may want to try changing this field to a sequence in your factory:
#factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
# Change to sequence to avoid duplicates
username = factory.Sequence(lambda n: "user_%d" % n)

Check the existence of instance with Django ManyToManyField

Hello Awesome People!
How to check whether a similar record is already in database with ManyToManyField():
class Room():
created_by = models.ForeignKey(User)
allowed_users = models.ManyToMany(User,related_name='rooms_as_guest')
is_active = models.BooleanField(default=True)
In my view before creating a new room, I want to make sure there wasn't an active room with exactly the same participants.
The following example won't work, it's just to show you what I expect
allowed_users = User.objects.filter(id__in=request.POST.get("ids",[]))
if allowed_users:
Room.objects.get_or_create(
created_by = request.user,
allowed_users = allowed_users, # won't work
is_active = True,
)
How could I proceed to do what I want?
This is because allowed_users is a ManyToMany Field and so, Room needs to created before adding Users to it.
See official documentation here:
https://docs.djangoproject.com/en/2.1/topics/db/examples/many_to_many/#many-to-many-relationships
Now the other problem is we need to get an existing room with the same set of users, which is already answered in a similar question here:
Django filter where ManyToMany field contains ALL of list
Try below:
from django.db.models import Count
allowed_users = User.objects.filter(id__in=request.POST.get("ids",[]))
if allowed_users:
room = Room.objects.filter(allowed_users__in=allowed_users).annotate(num_users=Count('allowed_users')).filter(num_users=len(allowed_users)).first()
if not room:
room = Room.objects.create(created_by=request.user, is_active=True)
room.allowed_users.add(*allowed_users)
You can't create an object like that with a ManyToMany field because the object must exist before you can create an intermediary object that represents your m2m relationship. You would have to do something like:
allowed_users = User.objects.filter(id__in=request.POST.get("ids", [])).order_by('id')
room = None
for r in up.rooms.filter(is_active=True):
if list(allowed_users) == list(r.allowed_users.all().order_by('id')):
room = r # get the existing room
break
if not room:
room = Room.objects.create(
created_by = request.user,
is_active = True
)
room.allowed_users.add(*allowed_users)
You'd have to ensure allowed_users are in the same order as room.allowed_users however.
Check the docs for more info

Update multiple fields of member of an instance simultaneously

Suppose I have a User model and this model
class modelEmployer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True, blank=True)
employer_image = models.ImageField(upload_to='images/', default='')
Now suppose i have an instance of modelEmployer and I would like to update the content of the user object in it. I know I can do this
instance.user.email = new value
instance.first_name = new value
instance.save()
I read we can run an update on a queryset (even if it returns one object). Now suppose I have a dictionary like this
dict = {"first_name : "John","last_name" : "deer",....}
How can i do something like this
modelEmployer.object.filter(instance.user.email=dict["email"]).update(only update the user objects as I would like to update the user object of this field using directly the dictionary. Any suggestions ?
You can use explicitly mention the relation and do it,
dict = {"user__first_name" : "John","user__last_name" : "deer",....}
And in the ORM, do as
modelEmployer.object.filter(instance.user.email=dict["email"]).update(**dict)
Hope this will solve your issue