Use get_object() and aggregate() in DetailView in Django - python

I have a DetailView in Django. In my template I'm printing the fields in my object dictionary, e.g. object.full_name.
But I also want some aggregates belonging to the object. If say I have a model Person and a model Group and the model Group has a foreign key to Person, can I then override get_object() in my DetailView to be something like:
def get_object(self):
return super(DetailView, self).get_object().aggregate(num_groups=Count('group_set'))
I've tested my suggestion but it doesn't work. I guess there must be some work-around to do this without having to override get_context_data() and populating variables as context['num_groups'] = Group.objects.filter(person=self.object).count(). This could be done but if I have more than one model with a foreign key to Person it would be smarter if I could just accomplish this with an aggregate as in my code example.

DetailView.get_object() returns model instance instance not the queryset. But you can populate any attribute of the object:
def get_object(self):
object = super(PersonView, self).get_object()
object.num_groups = object.group_set.all().count()
return object
Note the PersonView in the super() call. You should use the name of your class view here.
If you want get multiple counts related to person then you can do it with something like this:
counts = Person.objects.filter(pk=object.pk).aggregate(
num_groups=Count('group_set'), num_items=Count('item_set'))
object.num_groups = counts['num_groups']
object.num_items = counts['num_items']

Related

How to update a foreign key's attribute after creation of object in Django Model

I'm creating a REST API using Django that has two models that relate to each other: Item and Attribute. An Item has Attribute as a foreign key. The Attribute model has a field called number_of_uses which stores the number of items that attribute has associated it with. I wanted to know how I can update that number on the creation of a new Item. How can I update the field after the creation of the item, but before the API returns a response to the caller?
First of, are you sure this is One-To-Many?
Maybe check out this? model.save(). You can override the save method to increase the counter of the attribute.
Another possibility is to create a property field for number_of_uses. Something like this:
class Attribute(models.Model):
# fields
#property
def number_of_uses(self):
return Item.objects.filter(attribute = self).count()
# or attribute__field = self.field
This can also be serialized and is always accurate.

Django manager queries don't work in a chain of queries. AttributeError: 'QuerySet' object has no attribute <the manager method>

Problem: I've implemented a custom manager for a model with just one custom query set named get_by_tag and it's working fine if I use it this way:
ViewStatistic.objects.get_by_tag('some-tag-name').filter(user=user_id)
But when I change the order of queries, in this way:
ViewStatistic.objects.filter(user=user_id).get_by_tag('some-tag-name')
it doesn't work! and raises this error:
AttributeError: 'QuerySet' object has no attribute 'get_by_tag'
Am I missing something?! How can I do this in such a order?
P.S: The custom manager is something like this:
class MyCustomManager(models.Manager):
def get_by_tag(self, tag_name):
posts = Post.objects.filter(tags__pk=tag_name)
return super().get_queryset().filter(post__pk__in=posts)
If you want to use your queryset methods inside of queryset chain and not only directly after the manager, you should define them as methods of custom QuerySet class that you connect to a manager.
Two solutions are described in Django documentation Creating a manager with QuerySet methods.
Common part - a custom QuerySet class with queryset methods
class MyCustomQuerySet(models.QuerySet):
def get_by_tag(self, tag_name):
return self.filter(post__pk__in=Post.objects.filter(tags__pk=tag_name))
# more possible queryset methods ...
A) if your manager has only queryset methods and no other custom methods
    then you can create it simply from the QuerySet.
class MyModel(models.Model):
objects = MyCustomQuerySet.as_manager()
B) if your manager need also other methods that do not return a queryset:
class MyCustomManager(models.Manager):
... # other methods
class MyModel(models.Model):
objects = MyCustomManager.from_queryset(MyCustomQuerySet)()
When you say ViewStatistic.objects it returns the object of <django.db.models.manager.Manager>
In your case since it have derived class MyCustomManager having base class models.manager, so it return object of <your_app.models.MyCustomManager> which have get_by_tag function, and you can access get_by_tag.
For second case ViewStatistic.objects.filter return django.db.models.query.QuerySet object and ofcourse it hasn't no method named get_by_tag that's why you get AttributeError.
One more point related to queryset is
The result of refining a QuerySet is itself a QuerySet, so it’s possible to chain refinements together.
https://docs.djangoproject.com/en/3.0/topics/db/queries/#chaining-filters
In your case get_by_tag return QuerySet further you performed .filter() operation, which is fine.
The django official documentation link can be followed related to models Manager and query for more details.
https://docs.djangoproject.com/en/3.0/topics/db/queries/

Django - How to return all objects in a QuerySet in their subclass form, rather than their parent class form?

I have a class Set with a many-to-many relationship to Item. I have lots of 'set' objects all containing lots of 'items'.
However, the Item class has been subclassed to create Article, Podcast, Video, and Episode. Basically, everything on the database was originally an Item. If my_set is a Set instance, containing Items - how do I create a Queryset which returns those objects in their subclass form? Ie, rather than me getting a Queryset full of Item instances, I get a Queryset with Article, Episode, Video, Podcast instances.
How would I get `my_set.objects.all().as_subclass()' to work?
class Item(models.Model, AdminVideoMixin):
base_attributes = 'foo'
def as_episode(self):
return Episode.objects.get(slug=self.slug)
class Video(Item):
class specific fields
class Article(Item):
class specific fields
class Podcast(Item):
class specific fields
class Episode(Item):
class specific fields
class Set(Item):
front_page = models.BooleanField(max_length=300, blank=False, default=False, null=False)
items = models.ManyToManyField(Item, related_name='in_sets', through='SetMeta', max_length=5000,)
def get_absolute_url(self):
return reverse('foo')
def ordered(self):
return self.items.all().order_by('-itemOrder__order')
def episodes(self):
episode_list = []
for each_item in self.items.all():
episode_list.append(each_item.as_episode())
return episode_list
def __str__(self):
return self.title
As you can see I tried two methods - to write a model method on Item() which returns itself as an Episode - but this only worked for single instances rather than a Queryset. As such I wrote a method on Set which can perform that method on all items within the self, but this doesn't produce a Queryset, just a list, and it feels messy?
Update: have just skimmed the django-polymorphic documentation again, and it seems to be exactly what you want. So the rest of my answer is probably not very useful, unless you are prohibited from taking code out of django-packages
I don't think Django provides a way to express a queryset that returns objects of more than one model type. Querysets are supposed to map inro SQL queries, and I don't think SQL can return rows from more than one table mixed up. (I'm not an SQL expert, so I may be wrong). However, if you don't want a list, Python provides a means to take a queryset and apply a transformation to each Item instance it returns: a generator function. So, for example, you could code
def items_as_subclasses(qs):
for instance in qs:
try:
yield instance.video
continue
except Video.DoesNotExist:
pass
try:
yield instance.article
continue
except Article.DoesNotExist:
pass
try: ...
raise ProbableCodingError(
f"Item instance id={instance.id} does not have a known subclass"
)
and then write
for item_subclass_instance in items_as_subclasses(queryset):
# whatever
or indeed pass "items":items_as_subclasses( queryset) into a template rendering
context.
If there is a long list of possible subclasses it might be better to have a subclass_type field in the base class, and use that to go straight to the only valid subclass field.
There's a nullable OneToOne link from the base class to its particular subclass, so you can write querysets that interrogate subclasses.
Or you could investigate django-polymorphic, which I once skimmed, and which I vaguely remember is for this sort of usage.

Django - generic ModelAdmin definition

This is a newbie question.
I have multiple models that I would like to show in admin view. I would like to show all model's fields when viewing a list of records for a model.
I'd like to define a generic ModelAdmin subclass which will be able to list out all fields for a model. But not sure how to pass the class objects to it. admin.py looks like this
class ModelFieldsAdmin(admin.ModelAdmin):
def __init__(self, classObj):
self.classObj = classObj
self.list_display = [field.name for field in classObj._meta.get_fields()]
admin.site.register(Candy, ModelFieldsAdmin)
admin.site.register(Bagel, ModelFieldsAdmin)
How can I pass the classObj (which would be one of the models Candy or Bagel) , and also ensure that the get_list_display() picks up the value from self.list_display ?
This isn't the right approach. You shouldn't override __init__ for an admin class.
Rather, define get_list_display and access self.model:
def get_list_display(request):
return [field.name for field in self.model._meta.get_fields()]

Canonical way to do bulk create in django-rest-framework 3.x?

In 2.x we had a serializer which looked like:
class FooSerializer(serializers.ModelSerializer):
bar = serializers.PrimaryKeyRelatedField()
class Meta:
model = Foo
This effectively was able to handle bulk creates (passing a list as the body of a JSON post request). In 3.x, this endpoint is broken. I've tried to implement something similar to the docs on DRF
class FooListSerializer(serializers.ListSerializer):
def create(self, validated_data):
foos = [Foo(**item) for item in validated_data]
return Foo.objects.bulk_create(foos)
class FooSerializer(serializers.ModelSerializer):
bar = serializers.PrimaryKeyRelatedField(
queryset=Bar.objects.all()
)
class Meta:
model = Foo
list_serializer_class = FooListSerializer
And while this works for a single create request, when I attempt to pass a list I get the error:
AttributeError: 'FooListSerializer' object has no attribute 'object'
I've seen some hacks where __init__ is super'd, but it seems with the creation of the ListSerializer class in 3.x there has to be a cleaner way to do this. Thanks in advance.
You don't show how your code is making a FooSerializer instance. The Django REST Framework 3 documentation says:
To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.
So, it seems that your code should detect whether the data contains one instance or many, and:
serializer = FooSerializer() to handle one instance, or
serializer = FooSerializer(many=True) to handle a list of many instances.
Explicit is better than implicit :-)

Categories