Django: Get all blogs and their latest blog entries - django

Suppose I have these two models:
class Blog(models.Model):
name = models.CharField(max_length=64)
# ...
class Entry(models.Model):
blog = models.ForeignKey(Blog)
added = models.DateTimeField(auto_now_add=True)
# ...
Now I want to get a list of all blogs along with the latest blog entry for each respective blog. What's the best way to do it?
If it makes any difference, we can assume that each blog has at least one entry.

There is a roundabout (and very hackish) way to do this. If you don't mind de-normalizing you can add an optional latest_entry field to your Blog model. You can then override Entry.save() to update this field for the corresponding Blog instance every time an Entry instance is created. Alternately you can add a signal to do this.
def save(self, *args, **kwargs):
if not self.pk: #This object is being created, not updated.
self.blog.latest_entry = self
models.Model.save(self, *args, **kwargs)
Yep, it is not clean. But it will reduce the number of queries. Then you can do:
[(blog, blog.latest_entry) for blog in Blog.objects.all()]

You can use the latest() method of the related manager:
blogs = Blog.objects.all()
a_blog = blogs[0]
latest_entry = a_blog.entry_set.latest()
# or...
latest_entries = [blog.entry_set.latest() for blog in blogs]
Something like that.

Related

How to I automatically filter out is_deleted records in an associated table in Django?

I am using soft deletes on one of my models in Django, and I am overwriting the default manager to always return active records only, using something like:
class ActiveRecordManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_deleted=False)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveRecordManager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
objects = ActiveRecordManager()
All works well. However, when I do:
tag = Tag.objects.get(pk=100)
And then I try to get the associated photos:
photos = tag.photos.all()
Then I get photos that are deleted. I only want to return objects that are not deleted (so my regular objects list. I was reading about _base_mangers in Django, which seems to control this, but the documentation recommends against filtering objects out:
If you override the get_queryset() method and filter out any rows,
Django will return incorrect results. Don’t do that. A manager that
filters results in get_queryset() is not appropriate for use as a base
manager.
But what I am not clear about is how I am supposed to filter these results. Any thoughts?
UPDATE:
I was asked to explain how this question is different from this one:
How to use custom manager with related objects?
In this 8 year old question they mention a deprecated method. That deprecated method is superseded by the method I outline below (base_managers) which according to the documentation I should not use. If people think I should use it, can you please elaborate?
why not use custom query methods instead of overriding manager as it may produce problems for example in admin pages?
class ActiveModelQuerySet(models.QuerySet):
def not_active(self, *args, **kwargs):
return self.filter(is_deleted=True, *args, **kwargs)
def active(self, *args, **kwargs):
return self.filter(is_deleted=False, *args, **kwargs)
class Tag(models.Model):
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
class Photo(models.Model):
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name="photos")
is_deleted = models.BooleanField(default=False, db_index=True)
objects = ActiveModelQuerySet().as_manager()
you can then filter your models however you want
tag = Tag.objects.active(pk=100)
deleted_tags = Tag.objects.not_active()
photos = tag.photos.active()
also note that you need is_deleted attribute in all your models that have the soft delete functionality like Photo in your case

How to reference pk in DetailView

Hi i'm sure this had a simple solution but i cant find it! It must be required ALL the time!
To learn django i am writing simple app for me to log my learning points. So i have two models:
class Topic(models.Model):
title = models.CharField(max_length=40)
def __unicode__(self):
return self.title
class Meta():
ordering = ['title']
class Fact(models.Model):
note = models.CharField(max_length=255)
topic = models.ForeignKey('Topic')
def __unicode__(self):
return self.note
class Meta():
ordering = ['note']
I have template and url that will list ALL the topics.
When i see that list i want to be able to click on it [which i can do] and have that topic and all the facts linked to it (thourgh the foreign key appear) [would that technicaly be described as filtered query set of child objects?] I am using detailview.
url
url(r'^(?P<pk>\d+)/$', TopicDetailView.as_view(), name='facts'),
Here is the code of the detail view. Know i knows it knows the pk as it shows the right page when i take out the extracontext filter (and just take .all()). But i cant ref it no matter how many ways i try. I'd like something like this...
class TopicDetailView(DetailView):
model = Topic
template_name = 'study/topic_facts.html'
def get_context_data(self, **kwargs):
# Call the base implementation first to get a context
context = super(TopicDetailView, self).get_context_data(**kwargs)
# Add in a QuerySet of all the books
context['fact_list'] = Fact.objects.filter(topic='pk')
return context
I can do this if i put some logic and a filter in the template but that doesn't seem very proper to me and i feel i must be able to do this easily by adding the right extra context.
Help some poor newbie out! Many Thanks.
'pk' is just a string. You mean self.kwargs['pk'].
But actually you don't want to do this at all. The super class already adds the Topic object to the context: and you have a relationship between Topic and Fact. You can traverse this relationship in the template:
{% for fact in topic.fact_set.all %}
...
{% endfor %}
so you don't need to override get_context_data.

Django ModelMultipleChoiceField for another model's many-to-many

I'm working on a DIY application (Django 1.5) and I've reached a roadblock. The main models involved are Guide, Tool, Item, Step. A Guide can have many Tools and Items, and a Tool or Item can belong to many Guides. The same goes for a Step - it can have many Tools and Items, and a Tool or Item can belong to many Steps. A Guide has many Steps and a Step belongs to a Guide.
Guide many-to-many Items
Guide many-to-many Tools
Guide one-to-many Steps
Step many-to-many Items
Step many-to-many Tools
The roadblock...
At the Guide-level, I want the Tool and Item options to be limitless. But at the Step-level, I want the Tool and Item options to be limited to those assigned to the Guide it belongs to. Basically, when creating/editing a Step, I want to list checkboxes for all the Tools and Items available through the Guide. The user selects those that are needed for the current Step. Each Step will have different combinations of Tools and Items (thus the need for checkboxes).
I discovered the ModelMultipleChoiceField for the Step's ModelForm class. There I can specify a queryset. BUT, how do I gain access to the instance of the Guide model to retrieve its Tools and Items so that I can properly build selections? I would like to provide queries similar to what you would do in a View...
Guide.objects.get(pk=n).tools.all()
Guide.objects.get(pk=n).items.all()
How can I achieve that via ModelMultipleChoiceField? I hope I was able to explained this clearly.
Thanks in advance for any help.
class Tool(models.Model):
name = models.CharField(max_length=100)
...
class Item(models.Model):
name = models.CharField(max_length=100)
...
class Guide(models.Model):
models.CharField(max_length=100)
description = models.CharField(max_length=500)
tools = models.ManyToManyField(Tool, null=True, blank=True)
items = models.ManyToManyField(Item, null=True, blank=True)
...
class Step(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
guide = models.ForeignKey(Guide)
tools = models.ManyToManyField(Tool, null=True, blank=True)
items = models.ManyToManyField(Item, null=True, blank=True)
EDIT: 5/2
After further reading, it looks like I have to override the __init__ method of ModelMultipleChoiceField, where I gain a reference to self.instance, allowing me to create my query like, self.instance.guide.tools.all() and self.instance.guide.items.all(). And then create the fields via fields['field_name'].
I'm at work now so I won't be able to try this out until later tonight. I'll report back my findings.
What I ended up doing is the following. I defined a method in my ModelForm class for creating the ModelMultipleChoiceField. The reason is at the point of requesting the Create Step page, there is no Guide associated with the Step, not until you save (POST...assuming validation is successful). But I do have access to the slug field of the Guide that the Step will be created for. I get if from the URL. And the slug field is unique in my app. So I pass the slug from my view to the form via the new method I created. From there, I'm able to get the tools assigned to the guide and make those options available on the form, in my template.
forms.py
class NewStepForm(forms.ModelForm):
...
def create_tools_field(self, slug):
self.fields['tools'] = forms.ModelMultipleChoiceField(
queryset=Guide.objects.get(slug=slug).tools.all(),
widget=forms.CheckboxSelectMultiple(attrs={'class': 'unstyled'})
)
...
views.py
class NewStepView(View):
form_class = NewStepForm
initial = {'key': 'value'}
template_name = "guides/guide_step_new.html"
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(NewStepView, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
slug = kwargs.get('slug')
form = self.form_class(initial=self.initial)
form.create_tools_field(slug)
return render(request, self.template_name, {'form': form})

How can I write to the instance on the parent class from a subclass in Django models?

Following on from this question...
I have two primary models for my blog, Article and Link, and both are subclasses of Post. Simplifying a little, they look something like this:
class Post(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
post_date = models.DateField(db_index=True, auto_now_add=True)
class Article(Post):
feature_image = models.FileField(upload_to='feature_images')
class Link(Post):
link = models.URLField(verify_exists=True)
I want to collect over both Articles and Links, so in my view, I run Post.objects.order_by('post_date') and presto, I get the whole list--but only with the fields that are on Post. If I want to use the link in a Link instance, I can't.
I have the primary key, so I should be able to do something like Link.objects.get(pk=item.pk) and be set--but I'd have to know if this was a Link or an Article.
Can I create a post_type property on the parent model and write to it with the correct model name from the children?
I solved this in a totally different way in the end, by writing a custom manager for Post:
class PostManager(models.Manager):
def __get_final(self, pk):
for k in Post.__subclasses__():
if k.objects.filter(pk=pk).exists():
return k.objects.get(pk=pk)
return None
def __subclass_queryset(self, qs):
collection = []
for item in qs:
collection.append(self.__get_final(item.pk))
return collection
def all(self):
return self.__subclass_queryset(super(PostManager, self).all())
Now Post.objects.all() (or any other QuerySet operation I add to the manager, like order_by), and I'll get back a list of all of the objects, with their full set of specific fields. (I then reset the manager on the subclasses, so they're not saddled with these extra queries for routine operations.)

Django call function when an object gets added

Hay, i have a simple model
class Manufacturer(models.Model):
name = models.CharField()
car_count = models.IntegerField()
class Car(models.Model):
maker = ForeignKey(Manufacturer)
I want to update the car_count field when a car is added to a manufacturer, I'm aware i could just count the Manufacturer.car_set() to get the value, but i want the value to be stored within that car_count field.
How would i do this?
EDIT
Would something like this work?
def save(self):
if self.id:
car_count = self.car_set.count()
self.save()
The best way make something happen when a model is saved it to use a signal. Django's documentation does a good job of describing what signals are and how to use them: http://docs.djangoproject.com/en/dev/topics/signals/
I'm not sure why you need to make it a field in the model though. Databases are very good at counting rows, so you could add a model method to count the cars which would use a very fast COUNT() query.
class Manufacturer(models.Model):
name = models.CharField()
def car_count(self):
return Car.objects.filter(maker=self).count()
class Car(models.Model):
maker = ForeignKey(Manufacturer)
In light of the requirement added by your comment, you're back to updating a field on the Manufacturer model whenever a Car is saved. I would still recommend using the count() method to ensure the car_count field is accurate. So your signal handler could look something like this:
def update_car_count(sender, **kwargs):
instance = kwargs['instance']
manufacturer = instance.maker
manufacturer.car_count = Car.objects.filter(maker=self).count()
manufacturer.save()
Then you would connect it to both the post_save and post_delete signals of the Car model.
post_save.connect(update_car_count, sender=Car)
post_delete.connect(update_car_count, sender=Car)
The proper way to let the database show how many cars a manufacturer has, is to let the database calculate it in the view using aggregations.
from django.db.models import Count
Manufacturer.objects.all().annotate(car_count=Count(car)).order_by('car_count')
Databases are very efficient at that sort of thing, and you can order by the result as seen above.
I'm a tiny bit confused.
.. when a car is added to a manufacturer ..
In the code shown in your question, I'd guess, you save a car with some manufacturer, e.g.
car.maker = Manufacturer.objects.get(name='BMW')
car.save()
Then the save method of the Car class would need to update the car_count of the manufacturer (see Overriding predefined model methods for more details).
def save(self, *args, **kwargs):
if self.id:
self.maker.car_count = len(self.maker.car_set.all())
super(Car, self).save(*args, **kwargs)
Since this isn't the most elegant code, I'd suggest as #Josh Wright to look into signals for that matter.
P.S. You could also add a method on the Manufacturer class, but I guess, you want this attribute to live in the database.
class Manufacturer(models.Model):
name = models.CharField()
def _car_count(self):
return len(self.car_set.all())
car_count = property(_car_count)
...
The override in MYYN's answer won't work, since Car.id won't be set (and probably not included in the Manufacturer's car_set) until it's saved. Instead, I'd do something like:
def save(self, *args, **kwargs):
super(Car, self).save(*args, **kwargs)
self.maker.car_count = len(self.maker.car_set.all())
self.maker.save()
Which is untested, but should work.
Of course, the best way is to use Josh's solution, since that's going 'with the grain' of Django.