I've read that circular imports is a 'code smell' and is fundamentally a bad design choice. I have an app that has models, User, Deck, Hand. I want the User to be able to create a Hand without needing to create a Deck, but also give the User the choice to put the Hand in the Deck if wanted. So I end up with something like this:
(< means ForeignKey Relationship)
User < Deck < Hand
&&
User < Deck
&&
User < Hand
models.py:
class User(AbstractUser):
pass
class Deck(models.Model):
created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=100, unique=True,
blank=False, null=False)
user = models.ForeignKey('users.User', related_name='decks',
on_delete=models.CASCADE, null=False)
class Hand(models.Model):
created = models.DateTimeField(auto_now_add=True)
deck = models.ForeignKey('goals.Deck', related_name='hands', on_delete=models.CASCADE, null=True)
name = models.CharField(max_length=100, blank=False, null=False)
user = models.ForeignKey('users.User', related_name='hands', on_delete=models.CASCADE, null=False)
serializers.py:
class HandSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
deck = serializers.CharField(required=False, source='deck.name')
class Meta:
model = Hand
fields = ('url', 'id', 'created',
'deck', 'name', 'user')
extra_kwargs = {
'url': {
'view_name': 'goals:hand-detail',
}
}
class DeckSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
hands = HandSerializer(many=True, read_only=True)
class Meta:
model = Deck
fields = ('url', 'id', 'created', 'name', 'user')
extra_kwargs = {
'url': {
'view_name': 'goals:deck-detail',
}
}
class UserSerializer(serializers.HyperlinkedModelSerializer):
decks = DeckSerializer(many=True)
hands = HandSerializer(many=True)
...
Is this the correct approach API-design-wise in order to achieve what I want app-design-wise? If not, how should I go about doing this? And if so, how do I get around the circular import errors when I change user from a ReadOnlyField to a UserSerializer() field?
Edit:
I was thinking if this approach was bad or impossible with the circular imports, I could create a standard one way relationship like:
User --> Deck --> Hand
and have a default Deck that's hidden from the user so that User can still create a Hand without creating his/her own Deck because it's already been done by default (just hidden away). But this feels like a hack too and I don't know if this approach smells more than the initial.
This is correct. Suppose I want to request a Hand with ID 1. I would do a GET request for /api/hands/1 right? Do I really expect it to serialize a full user with all the hands of that user? Maybe. It just depends.
To get around it you would define something like:
MinimalUserSerializer
- Returns only email, username, and ID.
- Does not return hands.
And you would use that instead of your full UserSerializer that returns hands all the time.