3

I am pretty new to DRF/Django and want to create an endpoint that returns nested json from multiple models in the format:

    { 
        "site": {  
            "uuid": "99cba2b8-ddb0-11eb-bd58-237a8c3c3fe6",
            "domain_name": "hello.org"
            },

       "status": "live",

       "configuration": {   
            "secrets": [  
            {
             "name": "SEGMENT_KEY", # Configuration.name
             "value": [...] # Configuration.value
            },
           {
             "name": "CONFIG_KEY",
             "value": [...]
            },

        "admin_settings": {
            'tier'='trail',
            'subscription_ends'='some date',
            'features'=[]
        }

Here are the models:

class Site(models.Model):
    uuid = models.UUIDField(
        default=uuid.uuid4,
        editable=False,
        unique=True)
    domain_name = models.CharField(max_length=255, unique=True)
    created = models.DateTimeField(editable=False, auto_now_add=True)
    modified = models.DateTimeField(editable=False, auto_now=True)

class AdminConfiguration(models.Model):
    TRIAL = 'trial'
    PRO = 'pro'
    TIERS = [
        (TRIAL, 'Trial'), 
        (PRO, 'Professional'),
    ]
    site = models.OneToOneField(
        Site,
        null=False,
        blank=False,
        on_delete=models.CASCADE)
    tier = models.CharField(
        max_length=255,
        choices=TIERS,
        default=TRIAL)
    subscription_ends = models.DateTimeField(
        default=set_default_expiration)
    features = models.JSONField(default=list)


class Configuration(models.Model):
    CSS = 'css'
    SECRET = 'secret'
    TYPES = [
        (CSS, 'css'),
        (SECRET, 'secret')
    ]
    LIVE = 'live'
    DRAFT = 'draft'
    STATUSES = [
        (LIVE, 'Live'),
        (DRAFT, 'Draft'),
    ]
    site = models.ForeignKey(Site, on_delete=models.CASCADE)
    name = models.CharField(max_length=255, blank=False)
    type = models.CharField(
        max_length=255,
        choices=TYPES)
    value = models.JSONField(
        null=True)
    status = models.CharField(
        max_length=20,
        choices=STATUSES)

Logic behind serializer/viewset to achieve mentioned json:

  • retrieves lookup_field: uuid
  • filters query param: Configuration.status (either live or draft
  • filters AdminConfiguration on site id (something like AdminConfiguration.objects.get(Site.objects.get(uuid))
  • filters Configuration on type = secret

Here are my serializers:

class SiteSerializer(serializers.ModelSerializer):
    class Meta:
        model = Site
        fields = [
            'uuid',
            'domain_name'
        ]

class AdminSerializer(serializers.ModelSerializer):
    class Meta:
        model = AdminConfiguration
        fields = [
            'tier',
            'subscription_ends',
            'features'
        ]

class ConfigurationSubSerializer(serializers.ModelSerializer):    
    class Meta:
        model = Configuration
        fields = [
            'name',
            'value',
        ]

class SecretsConfigSerializer(serializers.ModelSerializer):
    site = SiteSerializer()
    admin_settings = AdminSerializer()
    status = serializers.CharField()
    configuration = ConfigurationSubSerializer(many=True, source='get_secret_config')

    class Meta:
        model = Configuration
        fields = [
            'site',
            'admin_settings',
            'status'
            'configuration'
        ]

    def get_secret_config(self, uuid):
        site = Site.objects.get(uuid=self.context['uuid'])
        if self.context['status'] == 'live' or self.context['status'] == 'draft':
            return Configuration.objects.filter(
                        site=site,
                      status=self.context['status'],
                        type='secret'
                    )

Viewset:

class SecretsViewSet(viewsets.ReadOnlyModelViewSet):
    model = Site
    lookup_field = 'uuid'
    serializer_class = SecertsConfigSerializer
    filter_backends = (DjangoFilterBackend,)
    filterset_fields = ['status'] #query params

    def get_serializer_context(self):
        return {
            'status': self.request.GET['status'],
            'uuid': self.request.GET['uuid']
        }

    def get_serializer(self, *args, **kwargs):
        kwargs['context'] = self.get_serializer_context()
        return CombinedConfigSerializer(*args, **kwargs)

What am I missing to achieve the desired output?

  • output from django shell:
from site_config.models import Site, AdminConfiguration, Configuration
from site_config.serializers import SecretsConfigSerializer

site = Site.objects.get(id=2)
s = SecretsConfigSerializer(site)
s.data

### OUTPUT ###
AttributeError: Got AttributeError when attempting to get a value for field `site` on serializer `SecretsConfigSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `Site` instance.
Original exception text was: 'Site' object has no attribute 'site'.
5
  • Please update the question with the output format that you are getting now. Commented Aug 9, 2021 at 22:25
  • 1
    @afonso I added the output from the django shell above - thanks! Commented Aug 9, 2021 at 22:33
  • SecretsConfigSerializer is configured as a model serializer for Configuration, so why are you using it for a Site object? Commented Aug 10, 2021 at 1:39
  • @bdbd good question... to be frank, I'm not sure what to pass into SecretsConfigSerializer - I tried passing in a Configuration object but getting the same error Commented Aug 10, 2021 at 2:15
  • You mean when using Configuration, you get Original exception text was: 'Site' object has no attribute 'site'.? Commented Aug 10, 2021 at 3:03

2 Answers 2

2

Why you don't try something more general and build your response separating the serializers like this (maybe you can use the same serializers in somewhere else):

def get(self, request, *args, **kwargs):
   resp = {
           'site': None,
           'status': None,
           'configuration': None,
           'admin_settings': None,
        }
   sites = models.Site.objects.all()
   resp['site'] = serializers.SitesSerializer(sites, many=True).data
   admin_settings = models.AdminConfiguration.objects.all()
   resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
   # and so
   return Response(resp, status=status.HTTP_200_OK)
Sign up to request clarification or add additional context in comments.

1 Comment

thank you for suggesting this! way simpler and more readable
1

You can try it like this. This will also help to find out errors:

def get(self, request, *args, **kwargs):
    resp = {
        "site": None,
        "status": None,
        "configuration": None,
        "admin_settings": None
    }
    sites = models.Site.objects.all()
    resp['site'] = serializers.SitesSerializer(sites, many=True).data
    if resp['site'].is_valid():
        admin_settings = models.AdminConfiguration.objects.all()
        resp['admin_settings'] = serializers.AdminConfigurationSerializer(admin_settings, many=True).data
        if resp['admin_settings'].is_valid():
            return Response(resp, status=status.HTTP_200_OK)
        return Response(resp['admin_settings'].errors, status=status.HTTP_404_NOT_FOUND)
    return Response(resp['site'].errors, status=status.HTTP_404_NOT_FOUND)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.