Django admin GenericForeignKey widget - django

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

Related

Is a Django model with parents that have a parent and child relationship among them?

To elaborate, I wish to set up a model where a blog post can have a like and a comment on the post can also be liked.
I could build two models, post_like and comment_like where they would both be children of blog post and comment respectively. Is there a way through which I could implement the above functionality with a single model? Something more generic. Where 1 model can be used to implement the like functionality?
class Like(models.Model):
post = models.ForeignKey(post..., default = None)
comment = models. ForeignKey(comment...default = None)
like_type = models.CharField(choices, default = None)
There is a field on models called GenericForeignKey for that.
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
https://docs.djangoproject.com/en/3.1/ref/contrib/contenttypes/#generic-relations

how to create django form with multiplechoicefield and add those value to database

what I want to achieve is user will submit 3 inputs in the form 1) name 2) dropdown to select technician, 3) multiselect dropdown to select multiple products. Once the user submit the details
it will generate one lead in database with value like name,foreignkey of selected technician and id of selected products in different table. I don't know how to achieve this below I have mentioned my approch to achieve what I want. Please let me know if the models need any changes and how I can write a view for the same.
models.py
class product(models.Model):
name = models.CharField(max_length=20)
class technician(models.Model):
name = models.CharField(max_length=20)
class lead(models.Model):
name = models.CharField(max_length=20)
technician = models.ForeignKey(technician,on_delete=models.SET_NULL,null=True) #only single selection
products = models.ManyToManyField(product) #user can select multiple product in dropdown
form.py
class leadForm(form.ModelForm):
products = forms.MultipleChoiceField(queryset=Product.objects.all())
technician = forms.CharField(max_length=30,choices=[(i.id,i.name) for i in Technician.objects.all().values('id','name')
class Meta:
model = lead
fields = ('name','technician')
You should use a ModelMultipleChoiceField [Django-doc] here. The But in fact you do not need to implement the models yourself. You can simply let the Django logic do the work for you.
In order to give a textual representation at the HTML end, you can override the __str__ functions of the models:
class Product(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Technician(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
class Lead(models.Model):
name = models.CharField(max_length=20)
technician = models.ForeignKey(Technician, on_delete=models.SET_NULL, null=True)
products = models.ManyToManyField(Product)
Then we can simply define our form with:
class LeadForm(form.ModelForm):
class Meta:
model = Lead
fields = '__all__'
Note: usually classes are written in PamelCase and thus start with an Uppercase.
You can here use a class-based CreateView [Django-doc] for example:
from django.views.generic.edit import CreateView
from app.models import Lead
from app.forms import LeafForm
class LeadCreateView(CreateView):
model = Lead
form_class = LeadForm
template_name = 'create_lead.html'

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.

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

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]

"reverse editing" of generic inlines in django admin

I have a django model named Event with a generic inline relation to Relationship, like this:
# models.py
class Person(models.Model):
...
class Role(models.Model):
...
class Event(models.Model):
...
class Relationship(models.Model):
person = models.ForeignKey(Person)
role = models.ForeignKey(Role)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey("content_type", "object_id")
# admin.py
class RelationshipInline(generic.GenericTabularInline):
model = Relationship
extra = 0
class EventAdmin(admin.ModelAdmin):
inlines = [RelationshipInline]
I'd like to find a way to edit the inlines not only from the event admin page, but from the people admin page too. SO far I have added the following code to display inlines in the people page too
class ReverseRelationshipInline(admin.TabularInline):
model = Relationship
class IndividualAdmin(admin.ModelAdmin):
inlines = [ReverseRelationshipInline]
But I get content_type and object_id fields in the form and it is not very informational for admin users since it is just references to primary keys. I would rather prefer to resolve and show the content_object(even if it is not editable, to know at list to what objects are people related to).
Any direction to recommend?
Thanks.
Your "ReverseRelationshipInline" has to be a GenericTabularInline, not a TabularInline. That's all :-)
UPDATE
I think I now understand what you're after, and my answer is:
You won't be able to edit the content object inline of Person, but you want to show it nicely, maybe even as a link to its change form.
Add a function to Relationship which returns such an HTML link, provide your own ModelForm to your inline and specify the fields you want, which now includes your new function value (read-only). Something like this (untested):
# models.py
from django.core import urlresolvers
class Relationship(models.Model):
...
def link_content_object_changeform(self):
obj = self.content_object
change_url = urlresolvers.reverse(
'admin:%s_%s_change' % (
obj._meta.app_label,
obj._meta.object_name.lower()
),
args=(obj.id,)
)
return u'%s' % (change_url, obj.__unicode__())
link_content_object_changeform.allow_tags = True
link_content_object_changeform.short_description = 'in relation to'
# admin.py
class ReverseRelationshipInlineForm(forms.ModelForm):
class Meta:
model = Relationship
fields = ('person', 'role', 'link_content_object_changeform')
readonly_fields = ('link_content_object_changeform',)
class ReverseRelationshipInline(admin.TabularInline):
model = Relationship
form = ReverseRelationshipInlineForm