17
\$\begingroup\$

I'm new to Django and have just started to learn the basics. I have some questions about how to structure the code, how relationships should be written, and where the business logic should live.

I have written a simple app that should do the following:

  1. Store Group and Member entities in a database.
  2. Maintain a many-to-many relationship between groups and members, as well as member role specific to each group.
  3. To create or update a group it should be possible send a request with the json structure shown below.

    {
      "name": "My group 1",
      "members": [
        { "id": 1, "role": "Leader" },
        { "id": 2, "role": "Regular" }
      ]
    }
    

Questions about the code

  1. Am I re-inventing the wheel with the custom update/create code in the GroupSerializer? Does what I'm trying to do already exist in Django and I have just not found the right approach?
  2. Is the update/create logic in the GroupSerializers misplaced? Should this be in the views.py file?
  3. In serializers.py, line 23, is there a better way than using "source=groupmember_set". I feel dirty hardcoding this string. In additional comments are of course welcome.

I appreciate any feedback that can improve the code and my knowledge further!

models.py

from __future__ import unicode_literals
from django.db import models

class Member(models.Model):
    name = models.CharField(max_length=50)
    def __unicode__(self):
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=50)
    members = models.ManyToManyField(Member, blank=True, related_name='groups', through="GroupMember")
    def __unicode__(self):
        return self.name

class GroupMember(models.Model):
    member = models.ForeignKey(Member, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    role = models.CharField(max_length=40)
    def __unicode__(self):
        return self.member.name + " " + self.group.name + " - " + self.role

serializers.py

from rest_framework import serializers
from models import Group, Member, GroupMember
from django.db import transaction


class MemberSerializer(serializers.ModelSerializer):

    class Meta:
        model = Member
        fields = ('id', 'name', 'groups')


class GroupMemberSerializer(serializers.ModelSerializer):
    id = serializers.ReadOnlyField(source='member.id')
    name = serializers.ReadOnlyField(source='member.name')

    class Meta:
        model = GroupMember
        fields = ('id', 'name', 'role')


class GroupSerializer(serializers.ModelSerializer):
    members = GroupMemberSerializer(source="groupmember_set", many=True, read_only=True)

    class Meta:
        model = Project
        fields = '__all__'
        depth = 1

    @transaction.atomic
    def update(self, instance, validated_data):
        # Ignore the fact that i delete and replace. Will diff in the future
        GroupMember.objects.filter(group=instance).delete()
        members = self.initial_data.get("members")
        for member in members:
            id = member.get("id")
            role = member.get("role")
            new_member = Member.objects.get(pk=id)
            GroupMember(group=instance, member=new_member, role=role).save()

        instance.__dict__.update(**validated_data)
        instance.save()
        return instance

    @transaction.atomic
    def create(self, validated_data):
        group = Group.objects.create(**validated_data)
        if "members" in self.initial_data:
            members = self.initial_data.get("members")
            for member in members:
                id = member.get("id")
                role = member.get("role")
                member_instance = Member.objects.get(pk=id)
                GroupMember(group=group, member=member_instance, role=role).save()
        group.save()
        return project

views.py

from rest_framework import viewsets
from models import Group, Member
from serializers import GroupSerializer, MemberSerializer


class GroupViewSet(viewsets.ModelViewSet):
    queryset = Group.objects.all()
    serializer_class = GroupSerializer


class MemberViewSet(viewsets.ModelViewSet):
    queryset = Member.objects.all()
    serializer_class = MemberSerializer
\$\endgroup\$
  • 2
    \$\begingroup\$ Thanks for posting this here. It's the first working example I've been able to find on the net for posting nested data with a through model. Mostly working anyway. ;-) \$\endgroup\$ – shanemgrey Dec 19 '17 at 22:40
  • 1
    \$\begingroup\$ Thank you so much for this code snippet! I have been wondering what I am doing wrong for hours until I found this example! \$\endgroup\$ – Ratul Mar 19 '18 at 7:05
  • \$\begingroup\$ You don't have to handle nested representations. \$\endgroup\$ – x-yuri Jan 16 '19 at 18:18
4
\$\begingroup\$

Django-organizations is a great way to do this, if you didn't want to reinvent the wheel. Comes with many things I assume you would need in the future out of the box.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you for the suggestion but it wasn't the organization domain specifically that I needed a solution for. More how to implement the relation in general. \$\endgroup\$ – Matt Jan 16 '19 at 8:42
1
\$\begingroup\$

You could replace source=groupmember_set with source=groups (the related_name of the ManyToMany field). I think it is a little bit cleaner and easier to understand.

One thing I don't understand is, why are you using model = Project in your GroupSerializer class?

| improve this answer | |
\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.