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.
Related
I'm new to Django. I want to make an API that automatically generate a value to a field when POST to this api with Django.
def gen_code(k):
return # a value
class generate_code(models.Model):
code = models.CharField(max_length=100, default="", unique=True)
generated_code = gen_code(code value) #generated with the value in code field
Something like the code above. How can I make that
You cannot do this in the declaration of the fields, as there is no "instance" at that point in time. You can override the default save() method to accomplish this:
class GeneratedCode(models.Model):
code = models.CharField(max_length=100, default="", unique=True)
generated_code = models.CharField(max_length=100, null=True)
def save(self, *args, **kwargs):
if not self.generated_code:
self.generated_code = gen_code(self.code)
super().save(*args, **kwargs)
This isn't perfect, but it should get you started. This code assumes that you always want to create a generated_code if it doesn't already exist, and you always want to generate it based on the current instance's "code" field.
It's also not a great idea to have a "default" value combined with unique=True. This will open you up to Unique constraint errors rather than having django complain that the field is mandatory - which it handles gracefully.
Thanks #Trent, It worked for me
class inviation_Master(models.Model):
invitation_key = models.CharField(max_length=6, null=True, blank=True)
def save(self, *args, **kwargs):
if not self.invitation_key:
self.invitation_key = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
super().save(*args, **kwargs)
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 have created a (kind of) singleton to put all the app parameters in my database:
class SingletonModel(models.Model):
def save(self, *args, **kwargs):
self.pk = 1
super(SingletonModel, self).save(*args, **kwargs)
#classmethod
def load(cls):
return cls.objects.all().get()
class Meta:
abstract = True
class AppParameters(SingletonModel, models.Model):
DEFAULT_BALANCE_ALERT_THRESHOLD = models.PositiveIntegerField(default=5)
# other parameters...
It worked pretty well, until I tried to use one of these parameters in a default attribute of a model field:
class Convive(models.Model):
balance_alert_threshold = models.IntegerField(
default=AppParameters.load().DEFAULT_BALANCE_ALERT_THRESHOLD,
blank=True,
null=True)
This seemed to work too, but when I use a script to reinitialise local data, the first manage.py migrate produce a DoesNotExist since my Singleton does not exist yet.
It happens because of a file importing Convive model.
How would you solve this?
Is there a way to "delay" the evaluation of the default field?
Thanks.
EDIT
After posting this, I think that if my code processes db queries at import time, something may be wrong with it...
Create a method that returns the default value,
def get_default_balance_alert_threshold():
return AppParameters.load().DEFAULT_BALANCE_ALERT_THRESHOLD
then use that method as your default.
class Convive(models.Model):
balance_alert_threshold = models.IntegerField(
default=get_default_balance_alert_threshold,
blank=True,
null=True,
)
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 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)