I added a new model to my app named SocialProfile, which is responsible for keeping social-related properties of a user which has a one-to-one relationship with UserProfile model. This is the SocialProfile model in models.py:
class SocialProfile(models.Model):
profile = models.OneToOneField('UserProfile', on_delete=models.CASCADE)
facebook_profiles = models.ManyToManyField('FacebookContact', related_name='synced_profiles', blank=True)
google_profiles = models.ManyToManyField('GoogleContact', related_name='synced_profiles', blank=True)
hash = models.CharField(max_length=30, unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.pk:
hash = gen_hash(self.id, 30)
while SocialProfile.objects.filter(hash=hash).exists():
hash = gen_hash(self.id, 30)
self.hash = hash
def __str__(self):
return str(self.profile)
Right now, I keep a record for synced facebook & google profiles. Now, the problem is that creating new objects does not actually add any record in the database. I cannot create instances with scripts or admin. In case of scripts, the following runs without errors but no record is created:
for profile in UserProfile.objects.all():
sp = SocialProfile.objects.create(profile=profile)
print(profile, sp)
SocialProfile.objects.count()
The prints are done, and look correct and the count() returns 0. I try creating objects in admin, but I get the following error:
"{{socialprofile object}}" needs to have a value for field "socialprofile" before
this many-to-many relationship can be used.
I think that is another problem, because if I comment the Many-to-Many relationships, it is done, without error (still no new records). I mentioned it just if it might help.
I have checked the database, the tables are there, no new migrations are detected either.
Any help and idea about what could be the problem would be appreciated!
You've overwritten the save method so that it never actually saves anything. You need to call the superclass method at the end:
def save(self, *args, **kwargs):
if not self.pk:
...
return super(SocialProfile, self).save(*args, **kwargs)
Related
I have a FK in my Django model, that needs to be unique for every existing model existing before migration:
class PNotification(models.Model):
notification_id = models.AutoField(primary_key=True, unique=True)
# more fields to come
def get_notifications():
noti = PNotification.objects.create()
logger.info('Created notifiactions')
logger.info(noti.notification_id)
return noti.notification_id
class Product(models.Model):
notification_object = models.ForeignKey(PNotification, on_delete=models.CASCADE, default=get_notifications)
When migrating, I get three PNotification objects saved into the database, however each existing Product is linked with notification_id=1, so each existing Product gets linked with the same PNotification object. I thought the method call in default would be executed for each existing Product?
How can I give each existing Product a unique PNotification object?
I also suspected that a new PNotification would be created wth your setup. It appears as if the method is being called on the class decleration and not instance creation.
Maybe an overridden save method is a better approach here? Note, you'll need to change the logic slightly for OneToOneFields:
class Product(models.Model):
...
def save(self, *args, **kwargs):
if not self.notification_object.all():
notification = PNotification.objects.create()
self.notification_object.add(notification)
super(Product, self).save(*args, **kwargs)
I am new to Python & Django, currently working on food delivery application.
I get the following error in admin when trying to add new Pizza object:
"" needs to have a value for field "item_ptr" before this many-to-many relationship can be used.
I have the following: in models.py
class Item(models.Model):
name = models.CharField(max_length=64)
price = models.DecimalField(decimal_places=2,max_digits=10, default=0)
size = models.CharField(choices=SIZES, max_length=64, default="R")
def __str__(self):
return f"{self.name}, {self.size}, ${self.price}"
class Pizza(Item, abstractToppingConfig):
type = models.ForeignKey(pizzaType, on_delete=models.CASCADE)
def save(self, *args, **kwargs):
if not self.id:
newItem = Item(name=self.name, price=self.price, size=self.size)
newItem.save()
self.item_ptr = newItem
super(Pizza, self).save(*args, **kwargs)
def __str__(self):
return f"{self.type} {self.name}, {self.size}"
I've looked up a few of the answers here and understand that the error is occurring due to the parent class not being saved at the time the Pizza object is created.
For that purpose, I am trying to override the model save method to store the Item object first and add the relationship, however any of this seems to be working.
Creating and saving the object in Python shell works just fine, but not happening when Pizza object is saved.
Django Version: 2.2.6
Full traceback: https://gist.github.com/Michelle-lele/7b1e60a7a1cf804bd74cfdaae5091c4e
For anyone, that might face the same issue. I was using clean() method in the abstract class, thus causing the above error.
Django ManyToMany model validation
I want to get value from foreign key from another model and subscribe it.
It works when field is declared as ForeignKey but when field is declared as ManyToManyField it not works.
How can I do it?
Please help.
class Document(models.Model):
project = models.ForeignKey(Project)
text = models.ForeignKey(Text, null=True, blank=True)
language = models.ManyToManyField(Language, null=True, blank=True)
def save(self, *args, **kwargs):
self.text = self.project.text #WORKS
self.language = self.project.language.all() #NOT WORKS
super(Document, self).save(*args, **kwargs)
Does something like this works?
class Document(models.Model):
project = models.ForeignKey(Project)
text = models.ForeignKey(Text, null=True, blank=True)
languages = models.ManyToManyField(Language) # no need to null/blank + put the name on plural if using a many to many relation.
def save(self, *args, **kwargs):
self.text = self.project.text
super(Document, self).save(*args, **kwargs)
# super() must be called BEFORE feeding your many to many.
project_languages = self.project.languages.all()
self.languages.add(*project_languages)
When using a many to many field attribute, you need to wait for your instance is created before adding values in your many to many attribute.
You do this with add, contrary to foreign key fields.
You need to use .add() for initializing ManyToManyField
Change this line
# use plural naming convention when its ManyToMany relation `language` should be `languages` in your Document model
self.language = self.project.language.all()
to
# call save method of super class before adding values to many-to-many
super(Document, self).save(*args, **kwargs)
p_language = self.project.language.all()
self.language.add(*p_language)
So you must call save before adding the many-to-many relationship. Since add immediately affects the database, you do not need to save afterwards.
I'm using Factory Boy for testing a Django project and I've run into an issue while testing a model for which I've overridden the save method.
The model:
class Profile(models.Model):
active = models.BooleanField()
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,
related_name='profiles')
department = models.ForeignKey(Department, null=True, blank=True)
category_at_start = models.ForeignKey(Category)
role = models.ForeignKey(Role)
series = models.ForeignKey(Series, null=True, blank=True)
status = models.ForeignKey('Status', Status)
def save(self, *args, **kwargs):
super(Profile, self).save(*args, **kwargs)
active_roles = []
active_status = []
for profile in Profile.objects.filter(user=self.user):
if profile.active:
active_roles.append(profile.role.code)
active_status.append(profile.status.name)
self.user.current_role = '/'.join(set(active_roles))
if 'Training' in active_status:
self.user.current_status = 'Training'
elif 'Certified' in active_status:
self.user.current_status = 'Certified'
else:
self.user.current_status = '/'.join(set(active_status))
self.user.save()
super(Profile, self).save(*args, **kwargs) ### <-- seems to be the issue.
The factory:
class ProfileFactory(f.django.DjangoModelFactory):
class Meta:
model = models.Profile
active = f.Faker('boolean')
user = f.SubFactory(UserFactory)
department = f.SubFactory(DepartmentFactory)
category_at_start = f.SubFactory(CategoryFactory)
role = f.SubFactory(RoleFactory)
series = f.SubFactory(SeriesFactory)
status = f.SubFactory(StatusFactory)
The test:
class ProfileTest(TestCase):
def test_profile_creation(self):
o = factories.ProfileFactory()
self.assertTrue(isinstance(o, models.Profile))
When I run the tests, I get the following error:
django.db.utils.IntegrityError: UNIQUE constraint failed: simtrack_profile.id
If I comment out the last last/second 'super' statement in the Profile save method the tests pass. I wonder if this statement is trying to create the profile again with the same ID? I've tried various things such as specifying in the Meta class django_get_or_create and various hacked versions of overriding the _generation method for the Factory with disconnecting and connecting the post generation save, but I can't get it to work.
In the meantime, I've set the strategy to build but obviously that won't test my save method.
Any help greatly appreciated.
J.
factory_boy uses the MyModel.objects.create() function from Django's ORM.
That function calls obj.save(force_insert=True): https://github.com/django/django/blob/master/django/db/models/query.py#L384
With your overloaded save() function, this means that you get:
Call super(Profile, self).save(force_insert=True)
[SQL: INSERT INTO simtrack_profile SET ...; ]
=> self.pk is set to the pk of the newly inserted line
Execute your custom code
Call super(Profile, self).save(force_insert=True)
This generates this SQL: INSERT INTO simtrack_profile SET id=N, ..., with N being the pk of the object
Obviously, a crash occurs: there is already a line with id=N.
You should fix your save() function, so that the second time you call super(Profile, self).save() without repeating *args, **kwargs again.
Notes:
Your code will break when you add an object through Django's admin, or anytime you'd use Profile.objects.create().
Since you don't modify self in your overloaded save() function, you should be able to remove the second call to super(Profile, self).save() altogether; although keeping it around might be useful to avoid weird bugs if you need to add more custom behavior later.
I'm trying to override a save method so that on creation of one model, an instance of the second model is created. However, it looks like the secondary model that I'm trying to create (Restaurant in this example) is being created twice. Why is that?
models.py
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
def __str__(self):
return "%s the place" % self.name
def save(self, *args, **kwargs):
super(Place, self).save(*args, **kwargs)
if Restaurant.objects.filter(place=self).count() == 0:
restaurant = Restaurant.objects.create(place=self)
class Restaurant(models.Model):
place = models.OneToOneField(
Place,
on_delete=models.CASCADE,
primary_key=True,
)
Your save method does not have proper indentation. I assume this was an error in cut and paste. With in that method.
if Restaurant.objects.filter(place=self).count() == 0:
restaurant = Restaurant.objects.create(restaurant=self)
This is essentially what get_or_create does but does atomically.
This method is atomic assuming correct usage, correct database
configuration, and correct behavior of the underlying database.
However, if uniqueness is not enforced at the database level for the
kwargs used in a get_or_create call (see unique or unique_together),
this method is prone to a race-condition which can result in multiple
rows with the same parameters being inserted simultaneously.
You can do the same in your own code of course with an atomic block but why bother. Just do
Restaurent.objects.get_or_create(place=self)
and isn't that place=self instead of restaurent=self as in your save method?
You can try:
obj.save(commit=False)
#change fields
obj.save()
First you will create save 'instance', do what you have to do, and then call the right save() method.