Can I add a manager to a manytomany relationship? - django

I have two models that has a manytomany relationship with a 'through' table in some way?
class Bike(models.Model):
nickname = models.CharField(max_length=40)
users = models.ManyToManyField(User, through='bike.BikeUser')
The BikeUser class
class BikeUser(models.Model):
bike = models.ForeignKey(Bike)
user = models.ForeignKey(User)
comment = models.CharField(max_length=140)
I would like to add functionality to the Bike class for working with users, is there a best practice way of doing this. I would like to avoid adding too many methods to the Bike class and rather have some kind of manager to work through
Something like:
bike.bikeusers_set.commonMethod()
or
bike.bikeusers.commonMethod()
What would be the best way to accomplish this?

Once you have the BikeUser model, you can add a custom manager to the model.
Something like:
class BikeUserManager(models.Manager):
def commonMethod():
pass
class BikeUser(models.Model):
bike = models.ForeignKey(Bike)
user = models.ForeignKey(User)
comment = models.CharField(max_length=140)
objects = BikeUserManager()
But you can only use it from the BikeUser Model:
BikeUser.objects.commonMethod()
What you want is to use this manager as a related manager:
http://docs.djangoproject.com/en/dev/topics/db/managers/#controlling-automatic-manager-types
Add the use_for_related_fields=True to the manager class.
class MyManager(models.Manager):
use_for_related_fields = True

use_for_related_fields is deprecated from django 2.0. Use for related fields is possible via base manager.
old:
class CustomManager(models.Model):
use_for_related_fields = True
class Model(models.Model):
custom_manager = CustomManager()
new:
class Model(models.Model):
custom_manager = CustomManager()
class Meta:
base_manager_name = 'custom_manager'
source of example
Remember about restrictions for get_queryset() method.

Code
def m2m_with_manager(related_manager, model_manager_name: str):
"""Replaces the default model manager tied to related manager with defined"""
model_manager = getattr(related_manager.model, model_manager_name)
return model_manager.filter(**related_manager.core_filters)
Example
for class Author and Books, where one Author can have multiple books
class Book:
author = FK(Author, related_name='books')
best = models.Manager(...)
Usage
wanted_qs = m2m_with_manager(author.books, model_manager_name='best')

Related

How to assign the attribute of SubFactory instead of the SubFactory itself

I need the attribute of the SubFactory instead of the object created by it.
# models.py
class User:
pass
class UserProfile:
user = models.OneToOneField(User)
class Job:
user = models.ForeignKey(User)
# factories.py
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
class UserProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = UserProfile
user = factory.SubFactory(UserFactory)
class JobFactory(factory.django.DjangoModelFactory):
class Meta:
model = Job
# for certain reasons, I want to use UserProfileFactory here but get the user generated from it
user = factory.SubFactory(UserProfileFactory).user # doesn't work but you get the idea
The best way is to combine class Params and factory.SelfAttribute:
class JobFactory(factory.django.DjangoModelFactory):
class Meta:
model = Job
class Params:
profile = factory.SubFactory(ProfileFactory)
user = factory.SelfAttribute("profile.user")
A parameter is used inside the factory, but discarded before calling the model.
This way:
You can provide a profile to the factory if you have it around: JobFactory(profile=foo)
You can set some sub-field of the profile' user: JobFactory(profile__user__username="john.doe")
I went with the approach below which might be useful to some:
user = factory.LazyFunction(lambda: UserProfileFactory().user)
That said, it's not documented if this is a legitimate way of using factories, so feel free to correct if this is wrong.

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.

Model relationships in Django

I am new to Django and databases and after reading the Django documentation on models I have the following question:
Let's say I have 3 models: VehicleName, CarManufacturer and TruckManufacturer. I am trying to create a database relationship where CarMaunfacturer has many VehicleNames and also TruckManufacturer has many VehicleNames. What is the relationship here and how to define it in Django? Is it as simple as define a models.ForeignKey(VehicleName) in both CarManufacturer and TruckManufacturer?
Thanks.
from django.db import models
class CarManufacturer(models.Model):
vehicle_name = models.ForeignKey(VehicleName) # IS THIS CORRECT???
# ...
pass
class TruckManufacturer(models.Model):
vehicle_name = models.ForeignKey(VehicleName) # IS THIS CORRECT???
# ...
pass
class VehicleName(models.Model):
# ...
To do exactly what you're describing:
I am trying to create a database relationship where CarMaunfacturer has many VehicleNames and also TruckManufacturer has many VehicleNames
You'd create a nullable foreign key on VehicleName to both of your Manufacturer models:
class CarManufacturer(models.Model):
# field definitions here
class TruckManufacturer(models.Model):
# field definitions here
class VehicleName(models.Model):
car_manufacturer = models.ForeignKey(CarManufacturer, blank=True, null=True)
truck_manufacturer = models.ForeignKey(TruckManufacturer, blank=True, null=True)
Then, instances of CarManufacturer or TruckManufacturer can get the names via the vehiclename_set attribute.
For a more advanced design, I would probably try to abstract the shared manufacturer behavior into a single model, then use multi-table inheritance:
class Manufacturer(models.Model):
# shared car and truck manufacturer fields go here
class CarManufacturer(Manufacturer):
# car manufacturer specific fields go here
class TruckManufacturer(Manufacturer):
# truck manufacturer specific fields go here
class VehicleName(models.Model):
manufacturer = models.ForeignKey(Manufacturer)
See the multi-table inheritance docs for full details.
I do not think you are understanding the manufacturer to vehicle relationship property. What I think you are trying to show is that a certain Vehicle belongs to a certain manufacturer.
This type of relationship would actually be defined in the Vehicle class, as a foreign key, called manufacturer, in the Vehicle class.
In the case you are defining many vehicles under a manufacturer, you just need to rename the property to car_model or something of the like and you should be fine.
I think you have the understanding mapped out well enough. Just remember that foreign keys are only a property of one table, and say nothing about the other table itself until the relationship is established there also.
If you're working with a larger relationship, with multiple objects, you should look into using the Many-to-many field described in the django documentation.
They have an example that shows how an Articles have many Publications:
class Publication(models.Model):
title = models.CharField(max_length=30)
# On Python 3: def __str__(self):
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
class Article(models.Model):
headline = models.CharField(max_length=100)
publications = models.ManyToManyField(Publication)
# On Python 3: def __str__(self):
def __unicode__(self):
return self.headline
class Meta:
ordering = ('headline',)

Django - Foreign key reverse creation

Imagine this model:
class ExercisePart(models.Model):
exercise = models.ForeignKey(Exercise)
sequence = models.IntegerField()
class Meta:
unique_together = (('exercise', 'sequence',),)
class Exercise(models.Model):
name = models.CharField(max_length=15)
From the admin interface I'd like to be able to create/link ExerciseParts through the Exercise page. I'd like to do that because I wish to avoid having to go on another page each time I want to add an ExerciseParts.
Is it possible ? How can I do that ?
You're looking for the inline admin feature.
admin.py
class ExercisePartInline(admin.TabularInline):
model = ExercisePart
class ExerciseAdmin(admin.ModelAdmin):
inlines = [ExercisePartInline]