Django create unique foreign key objects - python

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)

Related

Avoid validation of foreign key in django rest framework serializer

I write an API for the following models:
class TemplateProjectGroup(models.Model):
pass
class TemplateProject(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.CharField(max_length=1024, blank=True)
group = models.ForeignKey(TemplateProjectGroup, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
avatar_url = models.URLField(max_length=1024, blank=True)
The logic is following: User can create an instance of TemplateProject with non existed group field. So if group is not existed, it should be created with a specific ID. So, I have this serializer:
class TemplateProjectSerializer(serializers.ModelSerializer):
def create(self, validated_data):
template_project_group_id = validated_data.pop('group')
project = validated_data.pop('project')
group, _ = models.TemplateProjectGroup.objects.get_or_create(id=template_project_group_id)
template_project = models.TemplateProject.objects.create(**validated_data, group_id=group.id, project_id=project.id)
return template_project
def update(self, instance, validated_data):
template_project_group_id = validated_data.pop('group')
group, _ = models.TemplateProjectGroup.objects.get_or_create(id=template_project_group_id)
instance.save()
instance.update(**validated_data, group=group)
return instance
class Meta:
model = models.TemplateProject
fields = ('name', 'description', 'group', 'project', 'avatar_url')
and the view:
class TemplateProjectsView(generics.ListCreateAPIView):
pagination_class = None
serializer_class = serializers.TemplateProjectSerializer
def get_queryset(self):
return models.TemplateProject.objects.all()
It works well, when I try to retrieve list of objects, but I cannot create an object using this API, because I get following error:
Invalid pk "1" - object does not exist.
So, before creating an object, a validation is applied for all fields, and serializer cannot serialize this integer into an object because this object, which is referenced by foreign key, does not exist. I wrote a method validate_group(self, value), but exception raises before the execution point arrives this method. The more close point I could put a break in a debugger is method is_valid(self, raise_exception=False). I could create missing objects there, but I think, that would be a bad practice because this method actually doesn't has an aim for validating or preparing data.
How to properly create an object before it passes all validations?
One possible options is, define group explicitly as an integer field. This way, group field will not be tried to be validated as a TemplateProjectGroup instance.
class TemplateProjectSerializer(serializers.ModelSerializer):
group = serializers.IntegerField(source='group.id')
...
With this setup, you can get group id like this in create or update method of the serializer:
template_project_group_id = validated_data.pop('group').get('id')
Another option is, you could get or create a group instance in the view, by getting group id from the request, and then always pass an existing group id to the serializer, and expect an existing group id in the serializer. This would mean moving some of the validation logic to the view (you'd need to check at least if an integer is supplied for group field), but you wouldn't need to tweak your serializer.

Writing a custom manager for my model class with an existing obj instance

I am reading on how to write a custom manager for my model however it seems like I have a few questions. The reason I would like to add a custom manager to my class is because I would like to introduce a method called "customUpdate" which would basically check if the members in a dict are members of this class. This is what my code looks like so far.Then Ill post in some questions that I have
class modelEmployer(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
location = models.PointField(srid=4326,max_length=40, blank=True, null=True)
objects = GeoManager() # models.GeoManager()
Now this is what my manager class would like this is a rough sketch:
class customEmployerManager(models.Manager):
def customUpdate(dataDict):
# Check if the fields in this data are present in this model
for key in dataDict:
empInst = How do I get instance of class which filter returned ?
if not hasattr(empInst, key):
# This property is not present
dataDict.pop(key)
empInst.update(**dataDict) #Will this work ? Update only works with queryset
Now here are my questions
1- From the tutorials that I read I need to add customEmployerManager to my main model class as an object member like this objects = customEmployerManager() however I am currently using geodjango and I already have something there how do I add another customEmployerManager there ?
2-I would like my update method to be called when I do something like this
modelEmployer.objects.filter(....).customUpdate(xx)
In customUpdate how do I access the queryset so I can call .update(**dataDict) on it ? I am currently doing empInst.update(**dataDict) which will not work ? Also how do I get the instance of modelEmployer instance on which update is being called ?
You can override GeoManager like this:
class customEmployerManager(GeoManager):
def customUpdate(self, **dataDict):
# Check if the fields in this data are present in this model
empInst = self.get_queryset()
for i in empInst:
i.update(**dataDict)
# in models.py
class modelEmployer(models.Model):
objects = customEmployerManager()
...
def update(self, **kwargs):
for key, value in kwargs.items():
if hasattr(self, key):
setattr(self, key, value)
self.save()
You need to call the manager like this:
modelEmployer.objects.customUpdate(**dict)

Get value from another model in case of ManyToManyField

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.

django model create does not work

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)

Django save method called twice?

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.

Categories

Resources