Django & Graphene: How to handle bidirectional relationship with polmorphic models? - django

I have a Django model that looks like this (simplified of course):
from django.db import models
from polymorphic.models import PolymorphicModel
class Tournament(models.Model):
slug = models.CharField(max_length=100, unique=True)
class Event(PolymorphicModel):
tournament = models.ForeignKey(Tournament, related_name='events')
slug = models.CharField(max_length=100)
class PracticeEvent(Event):
pass
class MatchEvent(Event):
winner = models.CharField(max_length=100, null=True, blank=True, default=None)
Tournaments consist of two kinds of events: practice events, and matches. I'd like to expose this model using GraphQL, using Graphene. This is what I have come up with:
import graphene
from graphene_django import DjangoObjectType
from . import models
class TournamentType(DjangoObjectType):
class Meta:
model = models.Tournament
exclude_fields = ('id',)
class EventType(graphene.Interface):
tournament = graphene.Field(TournamentType, required=True)
slug = graphene.String(required=True)
class PracticeEventType(DjangoObjectType):
class Meta:
model = models.PracticeEvent
interfaces = (EventType,)
exclude_fields = ('id',)
class MatchEventType(DjangoObjectType):
class Meta:
model = models.MatchEvent
interfaces = (EventType,)
exclude_fields = ('id',)
extra_types = {PracticeEventType, MatchEventType}
class Query(graphene.ObjectType):
tournaments = graphene.List(TournamentType)
events = graphene.List(EventType)
# ... resolvers ...
schema = graphene.Schema(
query=Query,
types=schema_joust.extra_types,)
So far, so good; I can query events { ... } directly, and even the tournament is available. However, as there is no DjangoObjectType with model = models.Event, I can't query tournaments { events {...} }...
How can I fix this? I can't make EventType a DjangoObjectTpe, and I don't know to add the events field after the fact.

On their own, EventType.tournament and TournamentType.events aren't so hard. The first one is shown in the question, and the second one can be implemented like this:
class EventType(graphene.Interface):
slug = graphene.String(required=True)
class TournamentType(DjangoObjectType):
class Meta:
model = models.Tournament
exclude_fields = ('id',)
events = graphene.List(EventType)
def resolve_events(self, info):
return self.events.all()
graphene-django doesn't recognize the relationship, but declaring and resolving the field manually does the trick. To also get the reverse-field, which would work if we didn't need to reference TournamentType, I digged into graphene-django and found graphene_django.converter.convert_django_field_with_choices. This lets us define the field like this:
import graphene
from graphene_django import DjangoObjectType, converter, registry
from . import models
class EventType(graphene.Interface):
tournament = converter.convert_django_field_with_choices(
models.Event.tournament.field, registry.get_global_registry())
slug = graphene.String(required=True)

Perhaps a Union type is what you want, combined with declaring EventType explicitly to inherit from an interface:
import graphene
# Rename your existing EventType to EventTypeInterface and redefine like
class EventType(DjangoObjectType):
class Meta:
model = Event
interfaces = [EventTypeInterface]
class EventUnionType(graphene.Union):
#classmethod
def resolve_type(cls, instance, info):
if isinstance(instance, MatchEvent):
return MatchEventType
elif isinstance(instance, PracticeEvent):
return PracticeEventType
return EventType
class Meta:
types = [MatchEventType, PracticeEventType, EventType]

Related

Django Resf Framework: auto fill a field of a Model when POST?

Is there a way of filling some particular fields in a model using a field value of another model object?
For example, I thought of the following scenario:
1 - there are some models
from django.db import models
class Supplier(models.Model):
supplier = models.Charfield(max_length=50, unique=True)
class Object(models.Model):
object = models.Charfield(max_length=50)
supplier = models.ForeignKey(Supplier, on_delete=models.CASCADE, to_field="supplier")
class Transaction(models.Model):
object_id = models.ForeignKey(Object, on_delete=models.CASCADE)
supplier = models.Charfield(max_length=50)
2 - Those models are serialized
from . import models
from rest_framework.serializers import ModelSerializer
class SupplierSerializer(ModelSerializer):
class Meta:
model = models.Supplier
fields = '__all__'
class ObjectSerializer(ModelSerializer):
class Meta:
model = models.Object
fields = '__all__'
class TransactionSerializer(ModelSerializer):
class Meta:
model = models.Transaction
exclude = ('supplier',)
3 - And there is a view
from . import models, serializers
from rest_framework.viewsets import ModelViewSet
class TransactionApiViewset(ModelViewSet):
queryset = models.Transaction.objects.all()
serializer_class = serializers.TransactionSerializer
When submiting a post with 'object_id' field to Transaction Api, I'd like that the 'supplier' field in Transaction Model autofills with the 'supplier' field value of Object object related to 'object_id'.
I'd appreciate any help.
I think you could use property(or cached_property) method for supplier instead of saving as a field:
class Transaction(models.Model):
object = models.ForeignKey(Object, on_delete=models.CASCADE)
#property
def supplier(self):
return self.object.supplier.supplier
If you need to keep supplier saved in Transaction table, then just keep the model definition as you posted, and add a validate method in serializer:
class TransactionSerializer(ModelSerializer):
def validate(self, data):
object = Object.objects.get(id=data["object_id"])
data["supplier"] = object.supplier.supplier
return data
That would automatically update Transaction with new object related supplier's supplier field.

Exporting a field from another model, using foreign key

I've just set up the whole import-export thing and I just can't make it export a field from another model, using the foreign key.
models.py
from django.db import models
from django.contrib.auth.models import User
from datetime import date
from .validators import validate_file_size
# Create your models here.
class CORMeserii(models.Model):
CodCOR = models.CharField(max_length=25, primary_key=True, unique=True)
MeserieCor = models.CharField(max_length=50, unique=True)
def __str__(self):
return str(self.CodCOR + " - " + self.MeserieCor)
class Meta:
verbose_name_plural = "CORuri"
class Oferta(models.Model):
solicitant = models.ForeignKey(User, on_delete=models.CASCADE)
cor = models.ForeignKey(CORMeserii, on_delete=models.CASCADE)
dataSolicitare = models.DateField(default=date.today)
locuri = models.IntegerField()
agentEconomic = models.CharField(max_length=50)
adresa = models.CharField(max_length=150)
dataExpirare = models.DateField()
experientaSolicitata = models.CharField(max_length=200)
studiiSolicitate = models.CharField(max_length=200)
judet = models.CharField(max_length=20)
localitate = models.CharField(max_length=25)
telefon = models.CharField(max_length=12)
emailContact = models.EmailField(max_length=40)
rezolvata = models.BooleanField(default=False)
def __str__(self):
return str(self.cor)
admin.py
from django.contrib import admin
from .models import Oferta, CORMeserii
from import_export import resources
from import_export.admin import ImportExportMixin, ImportExportModelAdmin
import tablib
# Register your models here.
class CorImEx(resources.ModelResource):
class Meta:
model = CORMeserii
class CorAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('CodCOR', 'MeserieCor')
resource_class = CorImEx
class CorImExAdmin(ImportExportModelAdmin):
resource_class = CorImEx
class OferteImEx(resources.ModelResource):
class Meta:
model = Oferta
fields = ('id', 'solicitant', 'cor', 'oferta.cor.MeserieCor')
class OfertaAdmin(ImportExportMixin, admin.ModelAdmin):
list_display = ('id', 'solicitant', 'dataExpirare', 'dataSolicitare')
resource_class = OferteImEx
class OferteImExAdmin(ImportExportModelAdmin):
resource_class = OferteImEx
admin.site.register(Oferta, OfertaAdmin)
admin.site.register(CORMeserii, CorAdmin)
You can see it in the OferteImEx class - the field 'oferta.cor.MeserieCor'.
I want to export the MeserieCor field from the model CORMeserii - but I can't figure it out.
I tried with: 'oferta.cor.MeserieCor',
cor.MeserieCor', 'MeserieCor' (even though the last 2 ones don't make sense to me at all).
Is there any way to export that field somehow, even though it is from another model? (I'm pretty sure there is but I can't figure it out)
Thanks.
In Django you use double underscore (__) to follow relationship in lookups. It's in the documentation:
Django offers a powerful and intuitive way to “follow” relationships in lookups, taking care of the SQL JOINs for you automatically, behind the scenes. To span a relationship, just use the field name of related fields across models, separated by double underscores, until you get to the field you want.
Link: Lookups that span relationship
There is even an example in the django import export documentation how to follow relationship:
When defining ModelResource fields it is possible to follow model relationships:
class BookResource(resources.ModelResource):
class Meta:
model = Book
fields = ('author__name',)
Source: https://django-import-export.readthedocs.io/en/latest/getting_started.html#customize-resource-options

nested serializer showing null data

nested serializer showing null data
from rest_framework import serializers
from .models import PlayerTable, ChildTable
class ChildTableSerializer(serializers.ModelSerializer):
# x= ChildTable.objects.all().values
class Meta:
model = ChildTable
fields = ('season','goals','fk')
# fields =('fk',)
class PlayerTableSerializer(serializers.ModelSerializer):
player_details = ChildTableSerializer(many=True, read_only=True)
class Meta:
model = PlayerTable
fields = ('player_details',)
please help data getting by serializer is null
what is the field 'player-details'? It's not a field on your PlayerTable model. You need to use the name of the related field. In your case since you have a ForeignKey relationship ChildTable --> PlayerTable and you haven't specified the related_name, it's childtable_set. So if you do this it should work:
class PlayerTableSerializer(serializers.ModelSerializer):
childtable_set = ChildTableSerializer(many=True, read_only=True)
class Meta:
model = PlayerTable
fields = ('childtable_set',)
Alternatively, change your models naming to be more aligned with Django conventions:
class PlayerDetail(models.Model):
player = models.ForeignKey(Player, db_column="fk", related_name="player_details", null=True, blank=True, on_delete=models.CASCADE)
...
class Meta:
managed = False
db_table = "child_table"
class Player(models.Model):
name = models.CharField(db_column="player_name", ...)
class Meta:
db_table = "player_table"
then your serializer would work because the relation is player_details. This also has the advantage that when you do details.player you get the player object (now, you have to do details.fk but that actually doesn't return the foreign key value, it returns the Player object). Also your models have more pythonic names (Player not PlayerTable). Your code will be much more readable.

Extending TagBase in Django-Taggit

I have created the following TagBase and each category can have subcategory...
Will this work? How can I override its add function in the TaggableManager?
class Category(TagBase):
parent = models.ForeignKey('self', blank=True, null=True,
related_name='child')
description = models.TextField(blank=True, help_text="Optional")
class Meta:
verbose_name = _('Category')
verbose_name_plural = _('Categories')
django-taggit/docs/custom_tagging.txt describes how. You must define an intermediary model with a foreign key tag to your TagBase subclass.
from django.db import models
from taggit.managers import TaggableManager
from taggit.models import ItemBase
# Required to create database table connecting your tags to your model.
class CategorizedEntity(ItemBase):
content_object = models.ForeignKey('Entity')
# A ForeignKey that django-taggit looks at to determine the type of Tag
# e.g. ItemBase.tag_model()
tag = models.ForeignKey(Category, related_name="%(app_label)s_%(class)s_items")
# Appears one must copy this class method that appears in both TaggedItemBase and GenericTaggedItemBase
#classmethod
def tags_for(cls, model, instance=None):
if instance is not None:
return cls.tag_model().objects.filter(**{
'%s__content_object' % cls.tag_relname(): instance
})
return cls.tag_model().objects.filter(**{
'%s__content_object__isnull' % cls.tag_relname(): False
}).distinct()
class Entity(models.Model):
# ... fields here
tags = TaggableManager(through=CategorizedEntity)

Django admin GenericForeignKey widget

I'm creating a Django app where all the models can be related to each other in an order set by the user. I'm setting all this up using GenericForeignKeys. The kicker is that I need to be able to support multiple collections of these types of relationship/admin. So one object can have a more than one collection of related objects.
Does anyone know of a good GenericForeignKey widget for this situation? Preferably, it would be an autocomplete search that populates the admin form since I can end up having a large number of objects.
Here is the code for my app to get a better idea of what I mean.
from django.contrib import admin
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django import forms
# Models
class Base(models.Model):
title = models.CharField(max_length=255)
class Meta:
abstract = True
def __unicode__(self):
return self.title
class Related(Base):
""" A generic relationship model for relating resources.
"""
order = models.IntegerField(blank=True, null=True)
limit = models.Q(model = 'Apple') | models.Q(model = 'Orange') | models.Q(model = 'Pear')
content_type = models.ForeignKey(ContentType, related_name="related_%(class)s")
object_id = models.PositiveIntegerField(db_index=True)
object = generic.GenericForeignKey()
related_content_type = models.ForeignKey(ContentType, related_name="related_related_%(class)s", limit_choices_to = limit)
related_object_id = models.PositiveIntegerField(db_index=True)
related_object = generic.GenericForeignKey('related_content_type', 'related_object_id')
class Meta:
ordering = ('order',)
abstract = True
def __unicode__(self):
return self.object.title
class FreshFruit(Related):
pass
class OldFruit(Related):
pass
class Apple(Base):
pass
class Orange(Base):
pass
class Pear(Base):
pass
# Admin classes
class FreshFruitInline(generic.GenericStackedInline):
model = FreshFruit
extra = 1
# Admin classes
class OldFruitInline(generic.GenericStackedInline):
model = OldFruit
extra = 1
class AppleAdmin(admin.ModelAdmin):
inlines = [FreshFruitInline, OldFruitInline,]
admin.site.register(Apple, AppleAdmin)
class OrangeAdmin(admin.ModelAdmin):
inlines = [FreshFruitInline, OldFruitInline,]
admin.site.register(Orange, OrangeAdmin)
class PearAdmin(admin.ModelAdmin):
inlines = [FreshFruitInline, OldFruitInline,]
admin.site.register(Pear, PearAdmin)
I've searched and searched, and found widgets that do this for a ManyToMany relationship, but nothing for my situation.
Thanks for taking the time to look at this.
Have a look at Grappelli's generic foreign key widget, which works well:
django-grappelli/generic_2_2