Django - Filter QuerySet in models.py - python

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>

Related

Make the number of rows after narrowing-down

This is my serializer.
class MixSerializer(serializers.ModelSerializer):
pub_date = serializers.DateTimeField(format="%m/%d/%Y,%I:%M:%S %p")
new_order = #I want to get the number order
class Meta:
model = Mix
fields = ('id','pub_date','detail','user','u_key')
And I narrowing-down the rows like this below.
def get_queryset(self):
queryset = Mix.objects.all()
u_key = self.request.query_params.get('u_key')
if u_key is not None:
queryset = queryset.filter(u_key=u_key)
return queryset
For example, it returns the 30 items from 100 items.
so id should be (1,4,5,6,9,11,13...) like this,
However I want to get the number new_order (1,2,3,4,5,6,....)
I guess I should do some trick in Serializer?
or any other way ?
Any help appreciated.
Well ID is the actual ID in the database, which you don't want to change or override in your queryset (or elsewhere such as your template) because then you would be referring to a different model object, which will cause you problems.
If you want to use ID as some sort of ranking then you have some options, referencing my answer here
The easiest way is to use the forloop.counter in a template or enumerate in a view:
# template
{% for object in objects %}
# rank is {{ forloop0.counter }}
{% endfor %}
# views
for index, value in enumerate(queryset):
# order is the index variable
...
If you want to explicitly add the rank to the queryset then you can use annotation:
from django.db.models import Window, F
from django.db.models.functions import DenseRank
queryset = Mix.objects.annotate(
ranking=Window(
expression=DenseRank(),
order_by=[
F('id').desc(),
]))
If you want to get Order Table data, you have to create an Order Serializer and link to this MixSerilizer, Like this,
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ('id',)
class MixSerializer(serializers.ModelSerializer):
pub_date = serializers.DateTimeField(format="%m/%d/%Y,%I:%M:%S %p")
new_order = OrderSerializer()
class Meta:
model = Mix
fields = ('id','pub_date','detail','user','u_key','new_order')
models.py
class Mix(models.Model):
----
----
order = models.ForeignKey(Order, related_name=new_order, on_delete=models.CASCADE)
If want to get parent table data into a child table you have to pass "related_name" attribute in a models fields. and also that name in a child table sterilizer.

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 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: How to filter my models (by child)?

For my university project, I've described several models for e-commerce of goods:
Item, Book(Item), Stationery(Item), and ItemImage (related by ForeignKey for all Item-like models).
I need to filter the set of item images in the following way:
def home(request):
goods_images = ItemImage.objects.filter(is_active=True, is_main=True)
goods_images_books = ItemImage.objects.filter(is_active=True,
is_main=True)
goods_images_stationeries = ItemImage.objects.filter(is_active=True,
is_main=True)
return render(request, 'landing/home.html', locals())
The question is what the additional parameter I should add to filter()? Or is there another way of solving this problem?
class Entry(models.Model):
entry_title = models.ForeignKey(Title, on_delete=models.CASCADE)
some_other_field= models.CharField()
class Title(models.Model):
pass
Title.objects.filter(entry__some_other_field = 'something')

filter prefetch_related and select_related results

I have 3 models
models.py
class Book(models.Model):
name = models.CharField(max_length=255, primary_key=True)
...
class BookTranslation(models.Model):
book = models.ForeignKey(Book, related_name='translations')
language = models.CharField(max_length=2, choices=LANGUAGE_CHOICES)
...
class Chapter(models.Model):
book_translation=models.ForeignKey(BookTranslation,related_name='chapters')
...
I would like to create a DetailView for a book in just one language, my first approach was something like this:
views.py
class BookDetail(DetailView):
model = Book
def get_object(self):
return self.model.objects.select_related('translations').prefetch_related('translations__chapters').get(slug=self.kwargs['slug'], translations__language=self.kwargs['language'])
But this way i return all related BookTranslations and Chapters, not only those in the language i want...Is it possible to filter the select_related so that in my template, in {{book.translations}} i only have the translation in the language i asked for (same for chapters)?
Since you want to Get a Book with a specific BookTranslation, so its better that you base your Query on BookTranslation and get the Book object from it.
i.e. Use something like this
class BookDetail(DetailView):
model = Book
def get_object(self):
self.booktranslation = BookTranslation.objects.select_related('book').get(book__slug=self.kwargs['slug'],
language=self.kwargs['language'])
return self.booktranslation.book
You can then pass self.booktranslation as a context variable to access that in template.
You can try something like this to hit the database only once and fetch the related results:
class BookDetail(DetailView):
model = Book
def get_object(self):
return self.model.objects.filter(slug=self.kwargs['slug'],
translations__language=self.kwargs['language'],
translations__chapter__name=self.kwargs['ch_name'], ) \
.values('translations__language',
'translations__another_field',
'translations__chapter__name',
'translations__chapter__another_field', )[0]

Categories