Annotate query by existance in M2M - python

Schema: tables A and B with M2M between them.
I have some queryset QS0 of A objects and one exact instance of B object.
How to annotate QS0 with True if B is connected with A through M2M and False if it is not?
Thanks

Given the example models below
from django.db import models
class ModelA(models.Model):
title = models.CharField(max_length=100)
class ModelB(models.Model):
title = models.CharField(max_length=100)
a_objects = models.ManyToManyField(ModelA, related_name='b_objects')
You should in theory be able to annotate a queryset of ModelA objects on whether they are each linked to a ModelB object in the following way.
from django.db.models import Case, When, Value
b_object = ModelB.objects.get(id=some_id)
QS0 = ModelA.objects.annotate(is_linked_to_b=Case(When(b_objects__id=b_object, then=Value(True)), default=Value(False), output_field=BooleanField())
# QSO[some_index].is_linked_to_b should return either True or False.

Related

Django: How to use an annotation from a parent queryset?

I have an Item class which can be annotated using a custom queryset add_is_favorite_for method:
class ItemQuerySet(QuerySet):
def add_is_favorite_for(self, user):
"""add a boolean to know if the item is favorited by the given user"""
condition = Q(id__in=Item.objects.filter(favoriters=user).values("id"))
return self.annotate(is_favorite=Condition(condition)) # True or False
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
It works as expected. For example:
>>> user = User.objects.get(id=1)
>>> Item.objects.add_is_favorite_for(user) # each item has now a `is_favorite` field
Then, I added a Factory model and link Item model to it using a 1->N relationship:
class Factory(Model):
pass # ...
class Item(Model):
objects = Manager.from_queryset(ItemQuerySet)()
advised_in = models.ForeignKey(
Factory,
on_delete=models.CASCADE,
related_name="advised_items",
)
Now, I'd like to be able to return a Factory QuerySet, whose advised_items fields will all contain the is_favorite annotation too.
I don't know how to do this, I saw no example of such a thing in the doc, maybe I missed it.
You can work with a Prefetch object [Django-doc]:
from django.db.models import Prefetch
Factory.objects.prefetch_related(
Prefetch('advised_items', queryset=Item.objects.add_is_favorite_for(some_user))
)

Django REST Framework - Get reversed value of boolean field in serializer

I have 2 models:
class Model(models.Model):
...
related = models.ForeignKey(
'RelatedModel',
on_delete=models.CASCADE,
related_name='related_model'
)
class RelatedModel(models.Model):
...
flag = models.BooleanField()
I need to pass value of 'flag' attribute of RelatedModel in Model instance serializer and additionally this value must be reversed i.e. if it is 'True', I should return 'False' as boolean data type.
Already implemented this with method:
class ModelSerializer(serializers.ModelSerializer):
...
flag = serializers.SerializerMethodField()
#staticmethod
def get_flag(obj):
return not obj.related.flag
class Meta:
model = Model
fields = (
...
flag
)
But maybe there is opportunity to use only serializer fields like this but with reverse value?
flag = serializers.BooleanField(
source='related.flag', read_only=True
)
If you need to reverse the value you can' t use a BooleanField, the simplest solution is to use a SerializerMethodField as you have already done. Or you could also create a custom field class, but that is more complicated.

Django Rest Framework : How to define a ModelSerializer representing a Model A, when A depends itself of other models B and C?

Let's say we have three Models : ModelA, related to ModelB and ModelC.
ModelA is defined as :
class ModelA(models.Model):
field_b = models.ForeignKey(ModelB, on_delete=models.CASCADE)
field_c = models.ForeignKey(ModelC, on_delete=models.CASCADE)
other_field = models.CharField(max_length=30)
class Meta:
constraints = [
models.UniqueConstraint(fields=['field_b', 'field_c'], name='unique_modelA')
]
How to generate a ModelASerializer instance from instances of ModelB
and ModelC ?
Then, will there be a serialized representation of
field_b and field_c into ModelASerializer ?
The UniqueConstraint will
it be checked when calling .is_valid() onto ModelASerializer
instance ?
I tried the following :
class ModelASerializer(serializers.ModelSerializer):
field_b = ModelBSerializer(read_only=True)
field_c = ModelCSerializer(read_only=True)
other_field = serializers.CharField(required=False)
class Meta:
model = ModelA
fields = ('field_b', 'field_c', 'other_field',)
validators = [
UniqueTogetherValidator(
queryset=ModelA.objects.all(),
fields=['field_b', 'field_c'],
message='A Model A instance for this couple of ModelB and ModelC instances already exists.'
)
]
def create(self, validated_data):
"""
Create and return a new `ModelA` instance, given the validated data.
"""
return ModelA.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `ModelA` instance, given the validated data.
"""
instance.other_field= validated_data.get('other_field', instance.other_field)
instance.save()
return instance
But I cannot find any way to create the serializer :
model_b = ModelB()
model_c = ModelC()
model_b.save()
model_c.save()
other_field = "Dummy content"
First try
model_a_serializer = ModelASerializer(model_b, model_c, other_field)
The serializer is looking for an ID field and can't find it
Anyway, no data field being provided, we can't call .is_valid() onto the serializer, and thus, can't check the integrity constraint
Second try
model_b_serializer = ModelBSerializer(model_b)
model_c_serializer = ModelCSerializer(model_c)
data = {'model_b':model_b_serializer , 'model_c':model_c_serializer , 'other_field':other_field}
model_a_serializer = ModelASerializer(data=data)
if model_a_serializer.is_valid():
model_a_serializer.save()
Here, the serializer tries to recreate the ModelB and ModelC instances when is_valid() is called... And I don't want that.
Any ideas? Thank you very much by advance.
Not sure if I understood your question properly but...
How to generate a ModelASerializer instance from instances of ModelB and ModelC ?
If you want to generate a ModelA instance from ModelB and ModelCthen you should remove read_only=True argument and you have to explictly specify the relationship in create and update method.
class ModelBSerializer(serializers.ModelSerializer):
modelA_set = ModelASerializer() # this field name can be changed by adding related_name attribute in ModelA in models.py
class Meta:
model = ModelB
fields = ('modelA_set' , 'other_fields')
def create(self,validate_data):
modelA_data = validate_data.pop('modelA_data') # whatever field name you will use to send data for modelA
b = ModelB.objects.create(**validate_data)
for data in modelA_data:
modelA_instance = ModelA.objects.create(field_b=b ,**data)
return b
modelA_set is an automatically generated field name when we are trying to serialize ModelA from ModelB serializer . It can be changed by passing related_name='some_name' argument like this:
field_b = models.ForeignKey(ModelB, on_delete=models.CASCADE , related_name="field_a")
Then, will there be a serialized representation of field_b and field_c into ModelASerializer ?
Yes there will be.
Again correct me if I misunderstood your question.

Django model unique together both ways

Many questions already on this topic, but not what i'm searching for.
I have this Model:
class Options(TimeStampedModel)
option_1 = models.CharField(max_length=64)
option_2 = models.CharField(max_length=64)
class Meta:
unique_together = ('option_1', 'option_2')
Now I have a unique constraint on the fields.
Is there a way to also define this the other way around so that it doesn't matter what was option_1 and what was option_2
As example:
Options.create('spam', 'eggs') # Allowed
Options.create('spam', 'eggs') # Not allowed
Options.create('eggs', 'spam') # Is allowed but should not be
Thanks in advance!
I think a ManyToMany relation with a custom through table and an unique_together constraint on that table should do what you want.
Example code:
from django.db.models import Model, ForeignKey, ManyToManyField, CharField
class Option(Model):
name = CharField()
class Thing(TimeStampedModel):
options = ManyToManyField("Option", through="ThingOption")
class ThingOption(Model):
thing = ForeignKey(Thing)
option = ForeignKey(Option)
value = CharField()
class Meta:
unique_together = ('thing', 'option')
For Django 2.2+ it is recommended to use UniqueConstraint. In the docs there is a note stating unique_together may be deprecated in the future. See this post for its usage.
You can override create method, do something like
from django.db import models
class MyModelManager(models.Manager):
def create(self, *obj_data):
# Do some extra stuff here on the submitted data before saving...
# Ex- If obj_data[0]=="eggs" and obj_data[1]=="spam" is True don't allow it for your blah reason
# Call the super method which does the actual creation
return super().create(*obj_data) # Python 3 syntax!!
class MyModel(models.model):
option_1 = models.CharField(max_length=64)
option_2 = models.CharField(max_length=64)
objects = MyModelManager()

Django - Filter QuerySet in models.py

Is is possible to make queries from django models?
I have 2 models:
class Book(models.Model):
...
def copies_available(self):
pass
and
class BookCopy(models.Model):
...
book_category = models.ForeignKey(Book, related_name='copies')
issued_to = models.ForeignKey(EndUser, related_name='issued_books', null=True, blank=True)
I want copies_available() to return the number of BookCopy intances of that Book whose issued_to is None
This should work:
class Book(models.Model):
...
def copied_available(self):
return self.copies.filter(issued_to__isnull=True).count()
Yes just set limit_choices_to in the ForeignKey. From the docs:
"A dictionary of lookup arguments and values (see Making queries) that limit the available admin or ModelForm choices for this object. Use this with functions from the Python datetime module to limit choices of objects by date. For example:"
limit_choices_to = {'pub_date__lte': datetime.date.today}
I did this a couple years ago, so it may have changed a little bit:
You nee to create a BookManager class, and put the functionality there. Then assign the manager to your Book model's object variable, like this:
class BookManager(models.Manager):
def copied_available(self):
queryset = BookCopy.objects.filter(book_category=self.id).filter(issued_to is not None)
return queryset.count()
class Book(models.Model):
...
objects = BookManager()
So in your template, you can do something like:
<p> Copies: {{ thebook.copied_available }}</p>

Categories