Django - Foreign key reverse creation - django

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]

Related

How to create "related_name" relation with parent models of "through" model (about related_name inheritance)

I have 4 models including one M2M "through" model enabling to had an index:
class TargetShape(models.Model):
pass
class Page(models.Model):
target_shapes = models.ManyToManyField(TargetShape, through='PageElement', related_name='pages')
class PageElement(models.Model):
target_shape = models.ForeignKey(TargetShape, related_name='page_elements')
page = models.ForeignKey(Page, related_name='page_elements')
index = models.PositiveIntegerField(verbose_name='Order')
class WorkingSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='working_sessions')
I defined another model Resolution that enables me to link all of them together in order to create the following related_name relations:
working_session.resolutions
user.resolutions
target_shape.resolutions
page.resolutions
page_element.resolutions
To have it working, I had to declare:
class Resolution(models.Model):
# relations that would be needed from a DRY perspective:
page_element = models.ForeignKey(PageElement, related_name='resolutions')
working_session = models.ForeignKey(WorkingSession, related_name='resolutions')
# relations I want to exist to use user.resolutions, target_shape.resolutions, page.resolutions:
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='resolutions')
target_shape = models.ForeignKey(TargetShape, related_name='resolutions')
page = models.ForeignKey(Page, related_name='resolutions')
However, that's not very DRY. From a DRY perspective, I should be able to declare Resolution as linking to only PageElement and WorkingSession, and deduce / inherit the relation with the parent models:
class Resolution(models.Model):
page_element = models.ForeignKey(PageElement, related_name='resolutions')
working_session = models.ForeignKey(WorkingSession, related_name='resolutions')
But in that case, how can I create the following relations:
user.resolutions
target_shape.resolutions
page.resolutions
without going through:
user.working_sessions.resolutions
target_shape.page_elements.resolutions
page.page_elements.resolutions
All I can think about is to:
either declare all fields as I already did
or declare in the User, TargetShape and Page models a property that respectively returns:
Resolution.objects.filter(working_session__user=self)
Resolution.objects.filter(page_element__target_shape=self)
Resolution.objects.filter(page_element__page=self)
That would surely work, but that's not very elegant ... and not very Django-ish
Does anybody know a more Django-ish way to define this relation?
I found out another way: declare in the User, TargetShape and Page models a property that returns a queryset:
class Resolution(models.Model):
page_element = models.ForeignKey(PageElement, related_name='resolutions')
working_session = models.ForeignKey(WorkingSession, related_name='resolutions')
class PageElement(models.Model):
target_shape = models.ForeignKey(TargetShape, related_name='page_elements')
page = models.ForeignKey(Page, related_name='page_elements')
index = models.PositiveIntegerField(verbose_name='Order')
class WorkingSession(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='working_sessions')
class TargetShape(models.Model):
#property
def resolutions(self):
return Resolution.objects.filter(page_element__target_shape=self)
class Page(models.Model):
#property
def resolutions(self):
return Resolution.objects.filter(page_element__page=self)
target_shapes = models.ManyToManyField(TargetShape, through='PageElement', related_name='pages')

How can I include a ManyToMany field into django admin fieldsets?

I have two models:
Python 3.7
class ClassOne(models.Model):
name = models.CharField(max_length=10, default='')
class ClassTwo(models.Model):
name = models.CharField(max_length=20, default='')
class_ones = models.ManyToManyField(ClassOne)
Now I would like to show ClassOne in django-admin with all ClassTwo's listed. I already tried to create a Tabular (admin.TabularInline) or create a method in ClassOne as following:
def get_class_twos(self):
return self.classtwo_set.all()
and include that method in the fieldsets, but that did not work either. Neither did directly putting classtwo_set.all() or classtwo_set in the fieldset list.
You first should define TabularInline:
class ClassOneInLine(admin.TabularInline):
model = ClassTwo.classone.through
form = ClassOneForm
Then add it to admin class for ClassTwo:
class AuthorAdmin(admin.ModelAdmin):
inlines = (ClassOneInLine,)
And don't forget to register admin.

Serialize relation both ways with Django rest_framework

I wonder how to serialize the mutual relation between objects both ways with "djangorestframework". Currently, the relation only shows one way with this:
class MyPolys(models.Model):
name = models.CharField(max_length=20)
text = models.TextField()
poly = models.PolygonField()
class MyPages2(models.Model):
name = models.CharField(max_length=20)
body = models.TextField()
mypolys = models.ManyToManyField(MyPolys)
# ...
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPages2
# ...
class MyPolyViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPolys.objects.all()
serializer_class = srlz.MyPolysSerializer
class MyPages2ViewSet(viewsets.ReadOnlyModelViewSet):
queryset = testmodels.MyPages2.objects.all()
serializer_class = srlz.MyPages2Serializer
The many-to-many relation shows up just fine in the api for MyPages2 but nor for MyPolys. How do I make rest_framework aware that the relation goes both ways and needs to be serialized both ways?
The question also applies to one-to-many relations btw.
So far, from reading the documentation and googling, I can't figure out how do that.
Just do it like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields =('id','name','text','poly')
class MyPages2Serializer(serializers.HyperlinkedModelSerializer):
mypolys = MyPolysSerializer(many=True,read_only=True)
class Meta:
model = testmodels.MyPages2
fields =('id','name','body','mypolys')
I figured it out! It appears that by adding a mypolys = models.ManyToManyField(MyPolys) to the MyPages2 class, Django has indeed automatically added a similar field called mypages2_set to the MyPolys class, so the serializer looks like this:
class MyPolysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = testmodels.MyPolys
fields = ('name', 'text', 'id', 'url', 'mypages2_set')
I found out by inspecting an instance of the class in the shell using ./manage.py shell:
pol = testmodels.MyPolys.objects.get(pk=1)
pol. # hit the tab key after '.'
Hitting the tab key after the '.' reveals additional fields and methods including mypages2_set.

Can I add a manager to a manytomany relationship?

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')

Django filters - Using an AllValuesFilter (with a LinkWidget) on a ManyToManyField

This is my first Stack Overflow question, so please let me know if I do anything wrong.
I wish to create an AllValues filter on a ManyToMany field using the wonderful django-filters application. Basically, I want to create a filter that looks like it does in the Admin, so I also want to use the LinkWidget too.
Unfortunately, I get an error (Invalid field name: 'operator') if I try this the standard way:
# Models
class Organisation(models.Model):
name = models.CharField()
...
class Sign(models.Model):
name = models.CharField()
operator = models.ManyToManyField('Organisation', blank=True)
...
# Filter
class SignFilter(LinkOrderFilterSet):
operator = django_filters.AllValuesFilter(widget=django_filters.widgets.LinkWidget)
class Meta:
model = Sign
fields = ['operator']
I got around this by creating my own filter with the many to many relationship hard coded:
# Models
class Organisation(models.Model):
name = models.CharField()
...
class Sign(models.Model):
name = models.CharField()
operator = models.ManyToManyField('Organisation', blank=True)
...
# Filter
class MyFilter(django_filters.ChoiceFilter):
#property
def field(self):
cd = {}
for row in self.model.objects.all():
orgs = row.operator.select_related().values()
for org in orgs:
cd[org['id']] = org['name']
choices = zip(cd.keys(), cd.values())
list.sort(choices, key=lambda x:(x[1], x[0]))
self.extra['choices'] = choices
return super(AllValuesFilter, self).field
class SignFilter(LinkOrderFilterSet):
operator = MyFilter(widget=django_filters.widgets.LinkWidget)
I am new to Python and Django. Can someone think of a more generic/elegant way of doing this?
Why did you subclass LinkOrderFilterSet?
Maybe the connect way to use it is this:
import django_filters
class SignFilter(django_filters.FilterSet):
operator = django_filters.AllValuesFilter(widget=django_filters.widgets.LinkWidget)
class Meta:
model = Sign
fields = ['operator']
You can use this
class CircleFilter(django_filters.FilterSet):
l = []
for c in Organisation.objects.all():
l.append((c.id, c.name))
operator = django_filters.ChoiceFilter(
choices=set(l))
class Meta:
model = Sign
fields = ['operator']