How do I use a ModelManager on a Django ManyToMany through field? - django

Let's say I have the following models:
class Poll(model):
title = models.CharField()
class Option(model):
title = models.CharField()
polls = models.ManyToManyField(
Poll,
through='PollOption',
null=True,
blank=True,
related_name='options'
)
class PollOptionManager(models.Manager):
use_for_related_fields = True
def get_queryset(self):
return super(PollOptionManager, self).get_queryset().filter(
is_active=True
)
class PollOption(model):
poll = ForeignKey(Poll)
option = ForeignKey(Option)
is_active = BooleanField(default=True)
objects = PollOptionManager()
When I try to query Poll.options.all() I'm still getting Option instances for which PollOption.is_active is False. How can I get my model manager to appropriately filter my ManyToMany relationship based on a flag on the through field?

The problem is that the through model's (related) manager is never actually used in your scenario. In order to utilize the custom manager, you have to explicitly use it, e.g.:
class Poll(models.Model):
#property
def active_options(self):
return Option.objects.filter(id__in=self.polloption_set.values_list('option'))
Here, polloption_set filters out inactive options as intended. This, however, makes the manager kind of pointless because you can just as well put the extra filter in the custom property.

Related

Best practice when updating Django model fields? Is this what manager classes are for?

I am very new to Django and I am wondering what the best practice is for updating a field. Here is my model:
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
Right now I have a separate helper file, WebsiteHelper.py, with many other functions not related to the database in it, but also this function for updating a specific field in the DB:
def __mark_needs_redone(Website):
Website.update(is_awesome=True)
Is there a cleaner place for functions such as these to live, such as:
class WebsiteManager(models.Manager)
#Execute function here
Is this how managers are supposed to be used? If not, what is the best practice here?
If the field is on the model the form is handling, You can override the save() method you access to the actual instance.
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
def save(self, commit=True):
self.instance.is_awesome = True
return super().save(commit)
Don't forget the super().save(commit) call after because the parent takes care of the saving logic
Your update() is calling the wrong way, you should call it to the model queryset, not the instance.
If You need to call Your method for every save(), check the pre_save signal, but if You don't, use Manager.
class WebsiteManager(models.Manager):
def mark_needs_redone(self, pk):
self.get(pk=pk).update(is_awesome = True)
To your model Website add the Manager:
class Website(models.Model):
id = models.AutoField(primary_key=True)
url = models.TextField()
is_awesome = models.BooleanField(default=False)
objects = WebsiteManager()
And the usage is:
Website.objects.mark_needs_redone(pk=1)
That code will mark is_awesome as True for Website with pk=1

Django annotation on related model

Having these models (simplified):
class UserProfile(models.Model):
user = models.OneToOneField(User)
products = models.ManyToManyField(Product, through='UserProduct')
class Product(models.Model):
title = models.CharField(max_length=100, blank=False)
class UserProduct(models.Model):
user = models.ForeignKey(UserProfile)
product = models.ForeignKey(Product)
class Recipe(models.Model):
ingredients = models.ManyToManyField(Product, through='Ingredient')
class Ingredient(models.Model):
product = models.ForeignKey(Product)
recipe = models.ForeignKey(Recipe)
I need in some cases to get a list of recipes, marked on each ingredient, "whether it is user have that product.". And, maybe other calculated fields, according to given user.
Example of what i want:
>>> Recipe.objects.get_for_user(user=user)[0].ingredients[0].is_user_have
>>> True
But, of course, in other cases i don't want that field attached to ingredients.
I understand that the i need custom manager. But straightforward solution - add "is_user_have" as property to Ingredient model, define custom manager with get_for_user method, call base get_queryset and then in for-loop populate that field - doesn't work.
UPDATE 1
I figured out how to get annotations that i wanted, here is my custom manager for ingredients:
class UserIngredientsManager(models.Manager):
def get_queryset(self):
result = super(UserIngredientsManager, self).get_queryset()
return (result
.annotate(
user_have_count=models.Count(
models.Case(
models.When(
# Hardcoded !!!
product__userproduct__user_id=1,
then=True),
output_field=models.IntegerField())))
.annotate(
is_user_have=models.Case(
models.When(
user_have_count__gt=0,
then=models.Value(True)),
output_field=models.BooleanField(),
default=models.Value(False))))
But there are two problems:
I can't pass user to this manager (its hardcoded for testing)
I can't create proxy model for situtations when i want this annotations (see below), it only works when i replace default manager on Ingredient model.
This code doesn't work, for ingredients default related manager used instead:
class RecipeWithUserInfo(Recipe):
class Meta:
proxy = True
objects = UserRecipesManager()
ingredients = UserIngredientsManager()
It works only when i replace default manager on Ingredient model (but that not what i want):
class Ingredient(models.Model):
...
objects = UserIngredientsManager()

Django Tastypie Reference the Same ForeignKey Model More Than Once

Is there a way to reference the same ForeignKey model/resource more than once in Tastypie?
Assume the models:
class Case(models.Model):
name = models.CharField(max_length=10)
class Interaction(models.Model):
case = models.ForeignKey(Case, related_name="interaction_cases")
type = models.CharField(max_length=2, choices=TYPE_CHOICES)
Assume the TastyPie resources:
class CaseResource(ModelResource):
type_one_interactions = fields.ManyToManyField('TypeOneInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
type_two_interactions = fields.ManyToManyField('TypeTwoInteractionFullResource', 'interaction_cases', null=True, full_list=True, full=True)
class Meta:
queryset = Case.objects.all()
class TypeOneInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeOneInteractionResource, self).get_object_list(request).filter(type='A')
class TypeTwoInteractionResource(ModelResource):
case = fields.ForeignKey(Case,'case')
class Meta:
queryset = Interaction.objects.all()
def get_object_list(self, request):
return super(TypeTwoInteractionResource, self).get_object_list(request).filter(type='B')
Basically I am trying to create a single resource with two reverse resources to the same model with different data. When I access the CaseResource I see both TypeOneInteractionResource and TypeTwoInteractionResource in the result, but the data is not being filtered correctly.
I assume it has something to do with the "related_name" being the same and the way TastyPie does model joining internally. Has anybody been successful doing this? Is it even possible?
The reason is because get_object_list is not called at all when dehydrating the ToManyField for related resources (see https://github.com/toastdriven/django-tastypie/blob/master/tastypie/fields.py#L780).
Instead, you'd want to use the dehydrate_type_one_interactions and dehydrate_type_two_interactions methods on the CaseResource.
On the other hand, you can provide properties on the Case model that would return desired QuerySets and use those properties for attribute names in ManyToManyFields.

django: how do I query based on GenericForeignKey's fields?

I'm new in using GenericForeignKey, and I couldn't make it to work in a query statement. The tables are roughly like the following:
class Ticket(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
class Issue(models.Model):
scan = models.ForeignKey(Scan)
A scan creates one issue, an issue generates some tickets, and I made Issue as a foreign key to Ticket table. Now I have a Scan object, and I want to query for all the tickets that related to this scan. I tried this first:
tickets = Tickets.objects.filter(issue__scan=scan_obj)
which doesn't work. Then I tried this:
issue = Issue.objects.get(scan=scan_obj)
content_type = ContentType.objects.get_for_model(Issue)
tickets = Tickets.objects.filter(content_type=content_type, issue=issue)
Still doesn't work. I need to know how to do these kind of queries in django? Thanks.
The Ticket.issue field you've defined will help you go from a Ticket instance to the Issue it's attached to, but it won't let you go backwards. You're close with your second example, but you need to use the issue_id field - you can't query on the GenericForeignKey (it just helps you retrieve the object when you have a Ticket instance). Try this:
from django.contrib.contenttypes.models import ContentType
issue = Issue.objects.get(scan=scan_obj)
tickets = Ticket.objects.filter(
issue_id=issue.id,
issue_ct=ContentType.objects.get_for_model(issue).id
)
Filtering across a GenericForeignKey can by creating a second model that shares the db_table with Ticket. First split up Ticket into an abstract model and concrete model.
class TicketBase(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
class Meta:
abstract = True
class Ticket(TicketBase):
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
Then create a model that also subclasses TicketBase. This subclass will have all the same fields except issue which is instead defined as a ForeignKey. Adding a custom Manager allows it to be filtered to just a single ContentType.
Since this subclass does not need to be synced or migrated it can be created dynamically using type().
def subclass_for_content_type(content_type):
class Meta:
db_table = Ticket._meta.db_table
class Manager(models.Manager):
""" constrain queries to a single content type """
def get_query_set(self):
return super(Manager, self).get_query_set().filter(issue_ct=content_type)
attrs = {
'related_to': models.ForeignKey(content_type.model_class()),
'__module__': 'myapp.models',
'Meta': Meta,
'objects': Manager()
}
return type("Ticket_%s" % content_type.name, (TicketBase,), attrs)

Using computed fields in admin

I'm trying to use a runtime-computed field in my admin page. This works fine, but I'd like to allow sorting based for that field. Using Django 1.5 (dev), is this possible? I've been scouring the interweb but can't find anything indicating that it is possible.
class Guest(models.Model)
email = models.CharField(max_length=255)
class Invitation(models.Model)
guest = models.ForeignKey(Guest)
created_on = models.DateTimeField(auto_now_add=True)
class GuestAdmin(admin.ModelAdmin):
list_display = ["email", "latest_invitation_sent_on",]
def latest_invitation_sent_on(self, o):
try:
return o.invitation_set.all().order_by(
"-created_on")[0].created_on.strftime("%B %d, %Y")
except IndexError:
return "N/A"
I'd like to be able to enable sorting by latest_invitation_sent_on. Are there any methods of doing this nicely that I'm unaware of?
You should be able to annotate Guests with their latest invitation time and then order_by it (order_by uses the DB to sort and as long as you can provide a valid DB field, table or virtual it should work).
class GuestManager(models.Manager):
def get_query_set(self):
return super(GuestManager, self).get_query_set().annotate(latest_invite=Max("invitation_set__created_on"))
class Guest(models.Model)
email = models.CharField(max_length=255)
objects = GuestManager()
class Invitation(models.Model)
guest = models.ForeignKey(Guest)
created_on = models.DateTimeField(auto_now_add=True)
class GuestAdmin(admin.ModelAdmin):
list_display = ["email", "latest_invite",]
If you only need latest_invite annotation once in a while it makes sense to move it to a separate method or even manager.
class GuestManager(models.Manager):
def by_invitations(self):
return super(GuestManager, self).get_query_set().annotate(latest_invite=Max("invitation_set__created_on")).order_by('-latest_invite')
>>> Guest.objects.by_invitations()