Django -- foreign key doesn't seem to be working - python

I have the following models in my Django app:
class Dataset(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False
)
name = models.CharField(max_length=100, blank=False)
created_date = models.DateTimeField(auto_now_add=True)
last_modified = models.DateTimeField(auto_now=True)
class Imagefile(models.Model):
uuid = models.CharField(max_length=36, default="")
name = models.CharField(max_length=100, default="")
file = models.FileField(upload_to=image_directory_path)
dataset = models.ForeignKey(
Dataset,
on_delete=models.CASCADE,
related_name="imagefiles",
blank=False,
null=False,
)
class Annotation(models.Model):
image = models.FileField(upload_to=image_annotation_directory_path)
json = models.JSONField(default=dict)
imagefile = models.ForeignKey(
Imagefile,
on_delete=models.CASCADE,
related_name="annotation",
blank=False,
null=False,
)
What I want to achieve is to store one Dataset that has multiple Imagefiles, and each Imagefile can have either 0 or 1 related Annotation.
I am able to create a Dataset and Imagefiles that belong to it. When I try to create the Annotation, however, it is not registered in the Imagefile. In other words, the Annotation refers to the Imagefile, but the Imagefile sees an empty Annotation.
This is how I create my test case:
dataset = Dataset.objects.get(uuid=..., name='test_dataset')
new_imagefile = Imagefile.objects.create(
uuid=...,
dataset=dataset,
name='test_image.png'
)
new_imagefile.file.save('/some/existing/path/image.png', open('test_image.png', "rb"))
new_annot = Annotation.objects.create(imagefile=new_imagefile)
new_annot.image.save('/some/other/existing/path/annot.png', open('annot.png', "rb"))
When the above process is done, printing new_imagefile.annotation results in dataset.Annotation.None. I've been fighting with this for several hours, any hint is appreciated.

Wow, I found the problem right after I posted my question -- in the definition of Annotation, I should have used OneToOneField instead of ForeignKey. That fixed my problem.
I hope this can help other people that have the same problem.

Related

How to count posts that each hashtag has in django queryset

class Post(models.Model):
post_uuid=models.UUIDField(
default=uuid.uuid4, editable=False)
language = models.ForeignKey(
Languages, default=2, on_delete=models.CASCADE)
is_post_language = models.BooleanField(default=True)
description = models.CharField(max_length=400, null=True, blank=True)
hash_tag = models.ManyToManyField('HashtagName', through='Hashtag', related_name='hash_tag', blank=True)
created_on = models.DateTimeField(default=timezone.now)
def __str__(self):
return '{}'.format(self.post.id)
class HashtagName(models.Model):
Hashtagname = models.UUIDField(default=uuid.uuid4, editable=False)
hashtag_name = models.CharField(max_length=150, null=False, unique=True)
created_on = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.hashtag_name
class Hashtag(models.Model):
hashtag_uuid = models.UUIDField(default=uuid.uuid4, editable=False)
tag_name = models.ForeignKey(HashtagName,on_delete=models.CASCADE)
posts = models.ForeignKey(Post, on_delete=models.CASCADE)
created_on = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.hashtag_uuid
I have these classes.
Im making one api to get the list of trending hashtag
What im trying to do is when someone type
#planet
And i have list of these below hashtag
#planetearth Used in 7 posts
#planetjuypiter used in 5 posts
#planetmercury used in 3 posts
Etc
That starts with the keyword i have passed from frontend.
So i want to get the list of top 10 hashtag that starts with the keyword I passed from front end and based on in how many posts they are used.
Im trying this is it the most efficient?
hashtaglist = Hashtagname.objects.filter(hashtagname__startswith = passed name). annotate(posts = Count('hashtag').order_by('-posts')[:10]
What you write should work, but you made little error. You placed related name 'hash_tag' in Post for hash_tag field, while you trying to annotate Count('hashtag') in Hashtagname queryset. So the correct code will be:
hashtaglist = Hashtagname.objects.filter(
hashtagname__startswith = passed_name)
.annotate(posts = Count('hash_tag')\
.order_by('-posts')[:10]
if you trying to reach related records you should use related_name of field in related table if it's specified. If it's not specified you can use name of the related table + 'set' (for example post_set).

How to assign default value of the model based on the value of ForeignKey

I have the Account model were I store information about preferred units.
However I also want to allow user to change the units for particular exercise which by default should be Account.units.
Here are my models:
class Account(models.Model):
"""Model to store user's data and preferences."""
UNIT_CHOICES = [
('metric', 'Metric'),
('imperial', 'Imperial')
]
uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True, blank=False)
units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=UNIT_CHOICES[0], null=False, blank=False)
weight_metric = models.FloatField(null=True, blank=True)
height_metric = models.FloatField(null=True, blank=True)
weight_imperial = models.FloatField(null=True, blank=True)
height_imperial = models.FloatField(null=True, blank=True)
def __str__(self):
return self.owner.email
class CustomExercise(models.Model):
UNIT_CHOICES = [
('metric', 'Metric'),
('imperial', 'Imperial')
]
uuid = models.UUIDField(default=uuid.uuid4, unique=True, primary_key=True, editable=False)
created = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(Account, on_delete=models.CASCADE, null=False, blank=False)
preferred_units = models.CharField(max_length=255, choices=UNIT_CHOICES, default=owner.units, null=False, blank=False) # <- throws an error that "ForeignKey doesn't have units attribute."
name = models.CharField(max_length=255, null=False, blank=False)
measure_time = models.BooleanField(default=False)
measure_distance = models.BooleanField(default=False)
measure_weight = models.BooleanField(default=False)
measure_reps = models.BooleanField(default=False)
def __str__(self):
return f'{self.owner}:{self.name}'
As posted in code sample I tried to get that default value from ForeignKey, which not unexpectedly did not work out.
So my question is: what is the correct solution to implement this kind of feature?
I would not recommend storing duplicate values accross multiple models. You can easily access that value through a property method:
class CustomExercise(models.Model):
... # remove preferred_units field from model
#property
def preferred_units(self):
return self.owner.unit
Although you can not use it in queryset directly, still you can annotate the 'owner__unit' field in queryset or filter by it:
q = CustomExcercise.objects.annotate(preferred_units=F('owner__unit')).filter(preferred_units = 'kg')
q.values()
Displaying the value in Adminsite:
class CustomExerciseAdmin(admin.ModelAdmin):
fields = (..., 'preferred_units')
readonly_fields = ['preferred_units']
Two ways come to mind: overriding the model's save method or by using a pre_save signal. I would try the first one and if it doesn't work then the second one. The reason is that signals are notoriously difficult to debug so if you have alternatives you should always leave them as a last resort.
Ok so, I think this should work:
def save(self, *args, **kwargs):
self.preferred_units = self.owner.units
super(CustomExercise, self).save(*args, **kwargs
Otherwise:
#receiver(pre_save, sender=CustomExercise)
def assign_unit(sender, instance, **kwargs):
instance.preferred_units = instance.owner.units
The convention is to store your signals in signals.py in your app. Make sure to "activate" them from apps.py or they won't work. Here the docs.

Django Model Instance as Template for Another Model that is populated by Models

I'm trying to create a workout tracking application where a user can:
Create an instance of an ExerciseTemplate model from a list of available Exercise models. I've created these as models so that the user can create custom Exercises in the future. There is also an ExerciseInstance which is to be used to track and modify the ExerciseTemplate created by the user, or someone else. I'm stripping the models of several unimportant fields for simplicity, but each contains the following:
class Exercise(models.Model):
# Basic Variables
name = models.CharField(max_length=128)
description = models.TextField(null=True, blank=True)
def __str__(self):
return self.name
class ExerciseTemplate(models.Model):
# Foreign Models
workout = models.ForeignKey(
'Workout',
on_delete=models.SET_NULL,
null=True,
blank=True
)
exercise = models.ForeignKey(
Exercise,
on_delete=models.SET_NULL,
null=True,
blank=True
)
recommended_sets = models.PositiveIntegerField(null=True, blank=True)
class ExerciseInstance(models.Model):
""" Foreign Models """
exercise_template = models.ForeignKey(
ExerciseTemplate,
on_delete=models.SET_NULL,
null=True,
blank=True
)
workout = models.ForeignKey(
'Workout',
on_delete=models.SET_NULL,
null=True,
blank=True
)
""" Fields """
weight = models.PositiveIntegerField(null=True, blank=True)
reps = models.PositiveIntegerField(null=True, blank=True)
Create a WorkoutInstance from a WorkoutTemplate. The WorkoutTemplate is made up of ExerciseTemplates. But the WorkoutInstance should be able to take the WorkoutTemplate and populate it with ExerciseInstances based on the ExerciseTemplates in the WorkoutTemplate. Here are the models that I have so far:
class WorkoutTemplate(models.Model):
name = models.CharField(max_length=128)
description = models.TextField(null=True, blank=True)
date = models.DateTimeField(null=True, blank=True)
#category...
exercises = models.ManyToManyField(
Exercise,
through=ExerciseTemplate
)
def __str__(self):
return self.name
class WorkoutInstance(models.Model):
# Foreign Models
workout_template = models.ForeignKey(
'WorkoutTemplate',
on_delete=models.SET_NULL,
null=True,
blank=True
)
But this is where I get stuck. I'm not sure how to proceed. My intuition is one of the following:
I need to create a more simple architecture to do this. I'll take any suggestions.
I need to create a method within the model that solves this issue. If this is the case, I'm not sure what this would actually look like.
When you create a new WorkoutInstance object which references a given WorkoutTemplate object you get all its related ExerciseTemplate objects.
Then you just create a new object (row) for each ExerciseInstance in another model (table)
If you link your ExerciseInstance to WorkoutInstance via 'workout' you could do something like:
wt = WorkoutTemplate.get(id=1)
wi = WorkoutInstance.create(workout_template=wt)
for e in wt.exercisetemplate_set.all:
ExerciseInstance.create(exercise_template=e, workout=wi)
You can implent this in the method that creates the new WorkoutInstance or take a look at signals
https://docs.djangoproject.com/en/4.0/topics/db/optimization/#create-in-bulk

Django GenericForeignKey being deleted when it shouldn't

I'm having a problem where if I delete an object, another object is being deleted seemingly wrongly.
models.py
class Document(models.model):
file = models.FileField(upload_to=document_path, null=True, max_length=256)
ri_ct = models.ForeignKey(
ContentType, null=True, editable=False, related_name='documents_related_item', on_delete=models.PROTECT
)
ri_id = models.PositiveIntegerField(null=True, editable=False, db_index=True)
related_item = GenericForeignKey('ri_ct', 'ri_id')
class Invoice(models.model):
documents = GenericRelation(
Document, related_query_name='invoices', content_type_field='ri_ct', object_id_field='ri_id'
)
class BalanceUpdate(models.model):
related_item_ct = models.ForeignKey(
ContentType,
limit_choices_to=Q(app_label='main', model__in=('proformainvoice', 'invoice')),
related_name='balance_updates',
null=True,
on_delete=models.PROTECT,
)
related_item_id = models.PositiveIntegerField(null=True, blank=True, db_index=True)
related_item = GenericForeignKey(ct_field='related_item_ct', fk_field='related_item_id')
Now, if I do
Invoice.objects.filter(query).delete()
I'm getting a BalanceUpdate, which is related to Invoice, also deleted.
After a fair amount of debugging I've found that when deleting the Invoice, the document is being deleted (correctly due to the defined GenericRelation).
If we look at Collector.collect we have a recursive function which collects the objects to be deleted.
If I insert a print here, when the model is Document, sub_objs contains the BalanceUpdate object. How can this be the case? The Document obj surely should have sub_objs containing a BalanceUpdate, they are seemingly not linked?
Cheers in advance.

Delete entry from intermediary model if m2m changed

I've got a question. I want to delete the record in 'through' model, while editing Competition model in Django Admin. It is about editing m2m field 'competition_field'. Example: Competition with fields('Height', 'Width), and I will remove 'width' from m2m, and nothing will change in model 'FieldValues' I have tried everything I know, but without some succes.
This is my models.py
class Fields(models.Model):
field_name = models.CharField(max_length=100, unique=True)
class Competitions(models.Model):
competition_title = models.CharField(max_length=100, unique=True)
competition_field = models.ManyToManyField(Fields)
class Applications(models.Model):
application_applicant = models.ForeignKey(Applicant, on_delete=models.CASCADE)
application_competition = models.ForeignKey(Competitions, on_delete=models.CASCADE,)
application_value = models.ManyToManyField(Fields, through='FieldsValues')
class FieldsValues(models.Model):
catch_fields = models.ForeignKey(Fields, on_delete=models.CASCADE)
application = models.ForeignKey(Applications, on_delete=models.CASCADE)
value = models.TextField(null=True, default=0)

Categories

Resources