goals/filters.py
class PlanFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='iexact')

    class Meta:
        model = Plan
        fields = ['code']
goals/api/serializers.py
class IndicatorSerializer(serializers.ModelSerializer):
    image_small = serializers.ImageField(read_only=True)
    image_medium = serializers.ImageField(read_only=True)
    image_large = serializers.ImageField(read_only=True)
    target_code = serializers.CharField(read_only=True)
    target_name = serializers.CharField(read_only=True)
    goal_id = serializers.IntegerField(read_only=True)
    theme_code = serializers.CharField(read_only=True)
    theme_name = serializers.CharField(read_only=True)
    sector_code = serializers.CharField(read_only=True)
    sector_name = serializers.CharField(read_only=True)
    sector_type_code = serializers.CharField(read_only=True)
    sector_type_name = serializers.CharField(read_only=True)
    root_sector_id = serializers.IntegerField(read_only=True)
    root_sector_code = serializers.CharField(read_only=True)
    root_sector_name = serializers.CharField(read_only=True)
    goal_code = serializers.CharField(read_only=True)
    goal_name = serializers.CharField(read_only=True)
    plan_id = serializers.IntegerField(read_only=True)
    plan_code = serializers.CharField(read_only=True)
    plan_name = serializers.CharField(read_only=True)
    api_url = serializers.SerializerMethodField()
    progress_count = serializers.IntegerField(read_only=True)
    progress_preview = serializers.SerializerMethodField()

    class Meta:
        model = Indicator
        fields = '__all__'

    def get_progress_preview(self, obj):
        progress_preview = []
        if obj._prefetched_objects_cache['components']:
            for c in obj._prefetched_objects_cache['components']:
                for p in c.progress_preview:
                    progress_preview.append(model_to_dict(p))
        return progress_preview

    def get_api_url(self, obj):
        return self.context.get('request')\
            .build_absolute_uri(obj.api_url)
goals/filters.py
class ComponentFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='icontains')
    description = django_filters.CharFilter(lookup_expr='icontains')
    goal = django_filters.ModelChoiceFilter(
        name='indicators__target__goal', queryset=Goal.objects.all())
    progress_count = django_filters.NumberFilter(lookup_expr='gte')

    class Meta:
        model = Component
        fields = ['indicators', 'code', 'stats_available']
goals/api/serializers.py
class ProgressSerializer(serializers.ModelSerializer):
    area_code = serializers.CharField(read_only=True)
    area_name = serializers.CharField(read_only=True)
    area_type_id = serializers.IntegerField(read_only=True,
                                            allow_null=True)
    area_type_name = serializers.CharField(read_only=True)
    area_type_code = serializers.CharField(read_only=True)
    value_unit = serializers.CharField(read_only=True)

    class Meta:
        model = Progress
        exclude = []
goals/models.py
class Indicator(models.Model):
    theme = models.ForeignKey('goals.Theme', verbose_name=_('Theme'),
                              related_name='indicators', null=True, blank=True)
    sector = models.ForeignKey('goals.Sector', verbose_name=_('Sector'),
                               related_name='indicators', null=True, blank=True)
    target = models.ForeignKey(Target, verbose_name=_('Target'),
                               related_name='indicators', null=True, blank=True)
    name = models.CharField(_('Indicator'), max_length=255)
    code = models.CharField(_('Indicator number'), max_length=10)
    description = models.TextField(_('Indicator description'),
                                   blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/indicators/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})
    sectors_ids = ArrayField(
        models.IntegerField(), null=True, blank=True, editable=False,
        verbose_name=_('Sectors ids'), default=[])
    plan_id = models.IntegerField(_('Plan ID'), null=True, blank=True,
                                  editable=False)

    class Meta:
        verbose_name = _('Indicator')
        verbose_name_plural = _('Indicators')
        unique_together = ['code', 'target', 'sector', 'theme']

    def __str__(self):
        return '%s %s : %s' \
            % (self.plan_code, self.code, self.name)

    def save(self, *args, **kwargs):
        self.clean()
        if not self.slug:
            self.slug = self.get_slug()
        if self.theme:
            self.extras['theme_code'] = self.theme.code
            self.extras['theme_name'] = self.theme.name
        if self.sector:
            self.sectors_ids = self.sector.ancestors_ids + [self.sector_id]
            self.extras['sector_code'] = self.sector.code
            self.extras['sector_name'] = self.sector.name
            self.extras['sectors_codes'] = json.dumps(self.sector.ancestors_codes + [self.sector.code])
            self.extras['sectors_names'] = json.dumps(self.sector.ancestors_names + [self.sector.name])
            self.extras['sector_type_code'] = self.sector.type.code
            self.extras['sector_type_name'] = self.sector.type.name
            self.extras['root_sector_id'] = self.sector.get_root().id
            self.extras['root_sector_code'] = self.sector.get_root().code
            self.extras['root_sector_name'] = self.sector.get_root().name
        if self.target:
            self.extras['target_code'] = self.target.code
            self.extras['target_name'] = self.target.name
        if self.goal:
            self.extras['goal_id'] = self.goal.id
            self.extras['goal_code'] = self.goal.code
            self.extras['goal_name'] = self.goal.name
        if self.plan:
            self.plan_id = self.plan.id
            self.extras['plan_code'] = self.plan.code
            self.extras['plan_name'] = self.plan.name
        super(Indicator, self).save(*args, **kwargs)

    def clean(self):
        if self.theme and self.target:
            if self.theme.plan_id != self.target.goal.plan_id:
                raise ValidationError(
                    _('Theme and Target must belong to the same plan'))

    def get_slug(self):
        if not self.slug:
            slug = slugify(self.name[:50])
            return slug
        return self.slug

    @cached_property
    def api_url(self):
        try:
            return reverse('indicator-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''

    @cached_property
    def image_url(self):
        if self.image:
            return self.image.url

    @cached_property
    def image_small_url(self):
        if self.image_small:
            return self.image_small.url

    @cached_property
    def image_medium_url(self):
        if self.image_medium:
            return self.image_medium.url

    @cached_property
    def image_large_url(self):
        if self.image_large:
            return self.image_large.url

    @cached_property
    def theme_code(self):
        return self.extras.get('theme_code', '')

    @cached_property
    def theme_name(self):
        return self.extras.get('theme_name', '')

    @cached_property
    def sectors_names(self):
        if self.sector:
            return json.loads(self.extras.get('sectors_names', '[]'))
        return []

    @cached_property
    def sectors_codes(self):
        if self.sector:
            return json.loads(self.extras.get('sectors_codes', '[]'))
        return []

    @cached_property
    def sector_type_code(self):
        return self.extras.get('sector_type_code', '')

    @cached_property
    def sector_type_name(self):
        return self.extras.get('sector_type_name', '')

    @cached_property
    def sector_code(self):
        return self.extras.get('sector_code', '')

    @cached_property
    def sector_name(self):
        return self.extras.get('sector_name', '')

    @cached_property
    def root_sector_id(self):
        return int(self.extras.get('root_sector_id', '0')) or None

    @cached_property
    def root_sector_code(self):
        return self.extras.get('root_sector_code', '')

    @cached_property
    def root_sector_name(self):
        return self.extras.get('root_sector_name', '')

    @cached_property
    def target_code(self):
        return self.extras.get('target_code', '')

    @cached_property
    def target_name(self):
        return self.extras.get('target_name', '')

    @cached_property
    def goal(self):
        if self.target:
            return self.target.goal
        return None

    @cached_property
    def goal_id(self):
        return int(self.extras.get('goal_id', '0')) or None

    @cached_property
    def goal_code(self):
        return self.extras.get('goal_code', '')

    @cached_property
    def goal_name(self):
        return self.extras.get('goal_name', '')

    @cached_property
    def plan(self):
        if self.target:
            return self.target.goal.plan
        elif self.theme:
            return self.theme.plan
        return None

    @cached_property
    def plan_code(self):
        return self.extras.get('plan_code', '')

    @cached_property
    def plan_name(self):
        return self.extras.get('plan_name', '')

    def get_progress_count(self):
        return Progress.objects.filter(component__indicators=self.id).count()

    def get_progress_preview(self):
        return Progress.objects.filter(component__indicators=self.id)\
            .order_by('component__indicators', '-year')\
            .distinct('component__indicators')
goals/filters.py
class IndicatorFilter(django_filters.FilterSet):
    goal = django_filters.ModelChoiceFilter(name='target__goal',
                                            queryset=Goal.objects.all())
    description = django_filters.CharFilter(lookup_expr='icontains')
    data_source = django_filters.CharFilter(lookup_expr='icontains')
    agency = django_filters.CharFilter(lookup_expr='iexact')
    progress_count = django_filters.NumberFilter(lookup_expr='gte')
    sectors_ids = IntegerArrayFilter(lookup_expr='contains')

    class Meta:
        model = Indicator
        fields = ['plan_id', 'theme', 'target', 'sector', 'code']
goals/api/serializers.py
class TargetSerializer(serializers.ModelSerializer):
    image_small = serializers.ImageField(read_only=True)
    image_medium = serializers.ImageField(read_only=True)
    image_large = serializers.ImageField(read_only=True)
    goal_code = serializers.CharField(read_only=True)
    goal_name = serializers.CharField(read_only=True)
    plan_id = serializers.IntegerField(read_only=True)
    plan_code = serializers.CharField(read_only=True)
    plan_name = serializers.CharField(read_only=True)
    api_url = serializers.SerializerMethodField()

    class Meta:
        model = Target
        exclude = []

    def get_api_url(self, obj):
        return self.context.get('request')\
            .build_absolute_uri(obj.api_url)
goals/api/serializers.py
class SectorTypeSerializer(serializers.ModelSerializer):

    class Meta:
        model = SectorType
        fields = '__all__'
goals/models.py
class Goal(models.Model):
    plan = models.ForeignKey('goals.Plan', verbose_name='plan',
                             related_name='goals')
    code = models.CharField(_('Goal number'), max_length=10)
    name = models.CharField(_('Goal name'), max_length=255)
    caption = models.CharField(_('Caption'), max_length=255, blank=True)
    description = models.TextField(_('Goal description'), blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/goals/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Goal')
        verbose_name_plural = _('Goals')
        unique_together = ['code', 'plan']

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self.get_slug()
        self.extras['plan_name'] = self.plan.name
        self.extras['plan_code'] = self.plan.code
        super(Goal, self).save(*args, **kwargs)

    def get_slug(self):
        if not self.slug:
            slug = slugify(self.name[:50])
            return slug
        return self.slug

    @cached_property
    def api_url(self):
        try:
            return reverse('goal-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''

    @cached_property
    def image_url(self):
        if self.image:
            return self.image.url

    @cached_property
    def image_small_url(self):
        if self.image_small:
            return self.image_small.url

    @cached_property
    def image_medium_url(self):
        if self.image_medium:
            return self.image_medium.url

    @cached_property
    def image_large_url(self):
        if self.image_large:
            return self.image_large.url

    @cached_property
    def plan_name(self):
        return self.extras.get('plan_name', '') or self.plan.name

    @cached_property
    def plan_code(self):
        return self.extras.get('plan_code', '') or self.plan.code
goals/models.py
class Theme(models.Model):
    plan = models.ForeignKey('goals.Plan', verbose_name='Plans',
                             related_name='themes')
    name = models.CharField(_('Theme name'), max_length=255)
    code = models.CharField(_('Theme number'), max_length=10)
    caption = models.CharField(_('Caption'), max_length=255, blank=True)
    description = models.TextField(_('Theme description'), blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/themes/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Theme')
        verbose_name_plural = _('Themes')

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.extras['plan_name'] = self.plan.name
        self.extras['plan_code'] = self.plan.code
        if not self.slug:
            self.slug = self.get_slug()
        super(Theme, self).save(*args, **kwargs)

    @cached_property
    def image_url(self):
        if self.image:
            return self.image.url

    @cached_property
    def image_small_url(self):
        if self.image_small:
            return self.image_small.url

    @cached_property
    def image_medium_url(self):
        if self.image_medium:
            return self.image_medium.url

    @cached_property
    def image_large_url(self):
        if self.image_large:
            return self.image_large.url

    @cached_property
    def plan_name(self):
        return self.extras.get('plan_name', '') or self.plan.name

    @cached_property
    def plan_code(self):
        return self.extras.get('plan_code', '') or self.plan.code

    @cached_property
    def api_url(self):
        try:
            return reverse('theme-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''
goals/api/serializers.py
class GoalSerializer(ThemeSerializer):

    class Meta:
        model = Goal
        exclude = []
goals/api/serializers.py
class PlanSerializer(serializers.ModelSerializer):
    image_small = serializers.ImageField(read_only=True)
    image_medium = serializers.ImageField(read_only=True)
    image_large = serializers.ImageField(read_only=True)
    api_url = serializers.SerializerMethodField()

    class Meta:
        model = Plan
        fields = '__all__'

    def get_api_url(self, obj):
        return self.context.get('request')\
            .build_absolute_uri(obj.api_url)
goals/models.py
class Plan(models.Model):
    code = models.CharField(_('code'), max_length=10,
                            unique=True)
    name = models.CharField(_('Name'), max_length=255)
    caption = models.CharField(_('Caption'), max_length=255, blank=True)
    description = models.TextField(_('Description'), blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/goals/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Plan')
        verbose_name_plural = _('Plans')

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self.get_slug()
        super(Plan, self).save(*args, **kwargs)

    def get_slug(self):
        if not self.slug:
            slug = slugify(self.name[:50])
            return slug
        return self.slug

    @cached_property
    def api_url(self):
        try:
            return reverse('plan-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''

    @cached_property
    def image_url(self):
        if self.image:
            return self.image.url

    @cached_property
    def image_small_url(self):
        if self.image_small:
            return self.image_small.url

    @cached_property
    def image_medium_url(self):
        if self.image_medium:
            return self.image_medium.url

    @cached_property
    def image_large_url(self):
        if self.image_large:
            return self.image_large.url
goals/api/serializers.py
class AreaTypeSerializer(serializers.ModelSerializer):

    class Meta:
        model = AreaType
        fields = '__all__'
goals/filters.py
class ThemeFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='iexact')
    description = django_filters.CharFilter(lookup_expr='icontains')
    plan_code = django_filters.CharFilter(name='plan__code')

    class Meta:
        model = Theme
        fields = ['plan', 'code']
goals/models.py
class Sector(MPTTModel):
    themes = models.ManyToManyField('goals.Theme', verbose_name='Themes',
                                    related_name='sectors')
    parent = TreeForeignKey('self', null=True, blank=True,
                            related_name='children', db_index=True)
    name = models.CharField(_('Sector name'), max_length=255)
    code = models.CharField(_('Sector code'), max_length=20)
    type = models.ForeignKey('goals.SectorType',
                             verbose_name=_('Sector type'),
                             related_name='sextors')
    description = models.TextField(_('Sector description'), blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/sectors/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Sector')
        verbose_name_plural = _('Sectors')

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self.get_slug()
        if self.type:
            self.extras['type_code'] = self.type.code
            self.extras['type_name'] = self.type.name
        if self.parent:
            self.extras['parent_name'] = self.parent.name
        super(Sector, self).save(*args, **kwargs)

    def get_slug(self):
        if not self.slug:
            slug = slugify(self.name[:50])
            return slug
        return self.slug

    @cached_property
    def image_url(self):
        if self.image:
            return self.image.url

    @cached_property
    def image_small_url(self):
        if self.image_small:
            return self.image_small.url

    @cached_property
    def image_medium_url(self):
        if self.image_medium:
            return self.image_medium.url

    @cached_property
    def image_large_url(self):
        if self.image_large:
            return self.image_large.url

    @cached_property
    def type_code(self):
        return self.extras.get('type_code', '') or self.type.code

    @cached_property
    def type_name(self):
        return self.extras.get('type_name', '') or self.type.name

    @cached_property
    def parent_name(self):
        if self.parent:
            return self.extras.get('parent_name', '') or self.parent.name
        return ''

    @cached_property
    def ancestors_ids(self):
        return json.loads(self.extras.get('ancestors_ids', '[]'))\
            or [ancestor.id for ancestor in self.get_ancestors()]

    @cached_property
    def ancestors_codes(self):
        return json.loads(self.extras.get('ancestors_codes', '[]'))\
            or [ancestor.code for ancestor in self.get_ancestors()]

    @cached_property
    def ancestors_names(self):
        return json.loads(self.extras.get('ancestors_names', '[]'))\
            or [ancestor.name for ancestor in self.get_ancestors()]

    @cached_property
    def themes_codes(self):
        return json.loads(self.extras.get('themes_codes', '[]'))

    @cached_property
    def themes_names(self):
        return json.loads(self.extras.get('themes_names', '[]'))

    @cached_property
    def plans_ids(self):
        return json.loads(self.extras.get('plans_ids', '[]'))

    @cached_property
    def plans_codes(self):
        return json.loads(self.extras.get('plans_codes', '[]'))

    @cached_property
    def plans_names(self):
        return json.loads(self.extras.get('plans_names', '[]'))

    @cached_property
    def api_url(self):
        try:
            return reverse('sector-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''
goals/api/serializers.py
class SectorSerializer(serializers.ModelSerializer):
    api_url = serializers.SerializerMethodField()

    class Meta:
        model = Sector
        fields = '__all__'

    def get_api_url(self, obj):
        return self.context.get('request')\
            .build_absolute_uri(obj.api_url)
goals/api/serializers.py
class AreaSerializer(serializers.ModelSerializer):
    image_small = serializers.ImageField(read_only=True)
    image_medium = serializers.ImageField(read_only=True)
    image_large = serializers.ImageField(read_only=True)
    type_code = serializers.CharField(read_only=True)
    type_name = serializers.CharField(read_only=True)

    class Meta:
        model = Area
        fields = '__all__'
goals/models.py
class Target(models.Model):
    goal = models.ForeignKey(Goal, verbose_name=_('Goal'),
                             related_name='targets')
    code = models.CharField(_('Target number'), max_length=10)
    name = models.CharField(_('Target'), max_length=255)
    description = models.TextField(_('Target description'),
                                   blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/targets/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Target')
        verbose_name_plural = _('Targets')
        unique_together = ['code', 'goal']

    def __str__(self):
        return '%s %s : %s' % (self.plan_code, self.code, truncatechars(self.description, 50))

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self.get_slug()
        self.extras['goal_code'] = self.goal.code
        self.extras['goal_name'] = self.goal.name
        self.extras['plan_id'] = self.plan.id
        self.extras['plan_code'] = self.plan.code
        self.extras['plan_name'] = self.plan.name
        super(Target, self).save(*args, **kwargs)

    def get_slug(self):
        if not self.slug:
            slug = slugify(self.name[:50])
            return slug
        return self.slug

    @cached_property
    def api_url(self):
        try:
            return reverse('target-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''

    @cached_property
    def image_url(self):
        if self.image:
            return self.image.url

    @cached_property
    def image_small_url(self):
        if self.image_small:
            return self.image_small.url

    @cached_property
    def image_medium_url(self):
        if self.image_medium:
            return self.image_medium.url

    @cached_property
    def image_large_url(self):
        if self.image_large:
            return self.image_large.url

    @cached_property
    def goal_code(self):
        return self.extras.get('goal_code', '') or self.goal.code

    @cached_property
    def goal_name(self):
        return self.extras.get('goal_name', '') or self.goal.name

    @cached_property
    def plan(self):
        return self.goal.plan

    @cached_property
    def plan_id(self):
        return int(self.extras.get('plan_id', '0')) or self.goal.plan_id

    @cached_property
    def plan_code(self):
        return self.extras.get('plan_code', '') or self.goal.plan_code

    @cached_property
    def plan_name(self):
        return self.extras.get('plan_name', '') or self.goal.plan_name
goals/filters.py
class ProgressFilter(django_filters.FilterSet):
    indicator = django_filters.ModelChoiceFilter(
        name='component__indicators', queryset=Indicator.objects.all())
    target = django_filters.ModelChoiceFilter(
        name='component__indicators__target',
        queryset=Target.objects.all())
    area_code = django_filters.CharFilter(name='area__code')
    area_name = django_filters.CharFilter(name='area__name')
    area_type = django_filters.ModelChoiceFilter(
        name='area__type', queryset=AreaType.objects.all())
    area_type_code = django_filters.CharFilter(name='area__type__code')
    area_type_name = django_filters.CharFilter(name='area__type__name')

    class Meta:
        model = Progress
        fields = {
            'component': ['exact'],
            'area': ['exact'],
            'year': ['exact', 'lt', 'lte', 'gt', 'gte'],
            'fiscal_year': ['exact', 'lt', 'lte', 'gt', 'gte'],
            'value': ['exact', 'lt', 'lte', 'gt', 'gte']
        }
goals/filters.py
class TargetFilter(django_filters.FilterSet):
    description = django_filters.CharFilter(lookup_expr='icontains')

    class Meta:
        model = Target
        fields = ['goal', 'code']
goals/models.py
class SectorType(models.Model):
    code = models.CharField(_('Code'), max_length=20, unique=True)
    name = models.CharField(_('Name'), max_length=255)
    description = models.TextField(_('Description'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Sector Type')
        verbose_name_plural = _('Sector Types')

    def __str__(self):
        return self.name
goals/models.py
class Component(models.Model):
    YES = 'YES'
    NO = 'NO'
    PARTIALLY = 'PARTIALLY'
    UNKNOWN = 'UNKNOWN'
    STATS_AVAILABLE_CHOICES = (
        (YES, _('Yes')),
        (NO, _('No')),
        (PARTIALLY, _('Partially')),
        (UNKNOWN, _('Unknown')),
    )
    indicators = models.ManyToManyField('goals.Indicator',
                                        verbose_name=_('Indicators'),
                                        related_name='components')
    code = models.CharField(_('Component number'), max_length=10,
                            unique=True)
    name = models.CharField(_('Component name'), max_length=255)
    description = models.TextField(_('Component description'),
                                   blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/components/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    target_value = models.FloatField(_('Target value'), blank=True,
                                     null=True)
    value_unit = models.CharField(_('Value unit'), blank=True,
                                  max_length=50)
    stats_available = models.CharField(
        _('Statistics availble'), max_length=50, blank=True,
        choices=STATS_AVAILABLE_CHOICES, default=UNKNOWN)
    data_source = models.CharField(_('Data source'), max_length=255,
                                   blank=True)
    agency = models.CharField(_('Agency'), max_length=255, blank=True)
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Component')
        verbose_name_plural = _('Components')

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self.get_slug()
        super(Component, self).save(*args, **kwargs)

    def get_slug(self):
        if not self.slug:
            slug = slugify(self.name[:50])
            return slug
        return self.slug

    @cached_property
    def api_url(self):
        try:
            return reverse('component-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''

    @cached_property
    def image_url(self):
        if self.image:
            return self.image.url

    @cached_property
    def image_small_url(self):
        if self.image_small:
            return self.image_small.url

    @cached_property
    def image_medium_url(self):
        if self.image_medium:
            return self.image_medium.url

    @cached_property
    def image_large_url(self):
        if self.image_large:
            return self.image_large.url

    @cached_property
    def indicators_codes(self):
        return json.loads(self.extras.get('indicators_codes', '[]')) \
            or list(self.indicators.values_list('code', flat=True))

    @cached_property
    def indicators_names(self):
        return json.loads(self.extras.get('indicators_names', '[]')) \
            or list(self.indicators.values_list('name', flat=True))

    @cached_property
    def targets_ids(self):
        return json.loads(self.extras.get('targets_ids', '[]'))

    @cached_property
    def targets_codes(self):
        return json.loads(self.extras.get('targets_codes', '[]'))

    @cached_property
    def targets_names(self):
        return json.loads(self.extras.get('targets_names', '[]'))

    @cached_property
    def goals_ids(self):
        return json.loads(self.extras.get('goals_ids', '[]'))

    @cached_property
    def goals_codes(self):
        return json.loads(self.extras.get('goals_codes', '[]'))

    @cached_property
    def goals_names(self):
        return json.loads(self.extras.get('goals_names', '[]'))

    @cached_property
    def plans_ids(self):
        return json.loads(self.extras.get('plans_ids', '[]'))

    @cached_property
    def plans_codes(self):
        return json.loads(self.extras.get('plans_codes', '[]'))

    @cached_property
    def plans_names(self):
        return json.loads(self.extras.get('plans_names', '[]'))

    def get_progress_count(self):
        return Progress.objects.filter(component=self.id).count()
goals/filters.py
class SectorFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='iexact')
    description = django_filters.CharFilter(lookup_expr='icontains')

    class Meta:
        model = Sector
        fields = ['parent', 'themes']
goals/api/serializers.py
class ThemeSerializer(serializers.ModelSerializer):
    image_small = serializers.ImageField(read_only=True)
    image_medium = serializers.ImageField(read_only=True)
    image_large = serializers.ImageField(read_only=True)
    plan_id = serializers.IntegerField(read_only=True)
    plan_code = serializers.CharField(read_only=True)
    plan_name = serializers.CharField(read_only=True)
    api_url = serializers.SerializerMethodField()

    class Meta:
        model = Theme
        exclude = []

    def get_api_url(self, obj):
        return self.context.get('request')\
            .build_absolute_uri(obj.api_url)
goals/models.py
class AreaType(models.Model):
    code = models.CharField(_('Code'), max_length=20, unique=True)
    name = models.CharField(_('Name'), max_length=255)
    description = models.TextField(_('Description'), blank=True)
    topojson = models.FileField(_('TopoJSON'), blank=True, null=True,
                                upload_to=area_type_topo_path)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Area Type')
        verbose_name_plural = _('Area Types')

    def __str__(self):
        return self.name
goals/api/serializers.py
class ComponentSerializer(serializers.ModelSerializer):
    image_small = serializers.ImageField(read_only=True)
    image_medium = serializers.ImageField(read_only=True)
    image_large = serializers.ImageField(read_only=True)
    indicators_names = serializers.ListField(read_only=True)
    targets_ids = serializers.ListField(read_only=True)
    targets_codes = serializers.ListField(read_only=True)
    targets_names = serializers.ListField(read_only=True)
    goals_ids = serializers.ListField(read_only=True)
    goals_codes = serializers.CharField(read_only=True)
    goals_names = serializers.ListField(read_only=True)
    plans_ids = serializers.ListField(read_only=True)
    plans_codes = serializers.ListField(read_only=True)
    plans_names = serializers.ListField(read_only=True)
    progress_count = serializers.IntegerField(read_only=True)
    api_url = serializers.SerializerMethodField()

    class Meta:
        model = Component
        exclude = []

    def get_api_url(self, obj):
        return self.context.get('request')\
            .build_absolute_uri(obj.api_url)
goals/filters.py
class GoalFilter(ThemeFilter):

    class Meta:
        model = Goal
        fields = ['plan', 'code']
goals/models.py
class Progress(models.Model):
    component = models.ForeignKey(Component,
                                  verbose_name=_('Component'),
                                  related_name='progress')
    area = models.ForeignKey(Area, verbose_name=_('Area'),
                             related_name='progress')
    groups = ArrayField(
        models.CharField(max_length=50, blank=True), null=True,
        blank=True, verbose_name=_('Groups'), default=[])
    year = models.PositiveIntegerField(_('Year'))
    fiscal_year = models.CharField(_('Fiscal year'), max_length=9,
                                   blank=True)
    value = models.FloatField(_('Value'))
    remarks = models.TextField(_('Remarks'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Progress')
        verbose_name_plural = _('Progress')

    def __str__(self):
        return '%d:%d' % (self.year, self.value)

    def save(self, *args, **kwargs):
        self.extras['area_code'] = self.area.code
        self.extras['area_name'] = self.area.name
        self.extras['area_type_id'] = self.area.type_id
        self.extras['area_type_code'] = self.area.type_code
        self.extras['area_type_name'] = self.area.type_name
        self.extras['component_code'] = self.component.code
        self.extras['component_name'] = self.component.name
        self.extras['value_unit'] = self.component.value_unit
        super(Progress, self).save(*args, **kwargs)

    @cached_property
    def api_url(self):
        try:
            return reverse('progress-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''

    @cached_property
    def component_code(self):
        return self.extras.get('component_code', '')\
            or self.component.code

    @cached_property
    def component_name(self):
        return self.extras.get('component_name', '')\
            or self.component.name

    @cached_property
    def area_code(self):
        return self.extras.get('area_code', '') or self.area.code

    @cached_property
    def area_name(self):
        return self.extras.get('area_name', '') or self.area.name

    @cached_property
    def area_type_id(self):
        return int(self.extras.get('area_type_id', 0))\
            or self.area.type_id

    @cached_property
    def area_type_code(self):
        return self.extras.get('area_type_code', '')\
            or self.area.type_code

    @cached_property
    def area_type_name(self):
        return self.extras.get('area_type_name', '')\
            or self.area.type_name

    @cached_property
    def value_unit(self):
        return self.extras.get('value_unit', '')
goals/filters.py
class AreaFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(lookup_expr='iexact')
    type = django_filters.CharFilter(lookup_expr='iexact')

    class Meta:
        model = Area
        fields = ['code', 'level']
goals/models.py
class Area(MPTTModel):
    parent = TreeForeignKey('self', null=True, blank=True,
                            related_name='children', db_index=True)
    code = models.CharField(_('Area code'), max_length=20, unique=True)
    name = models.CharField(_('Area name'), max_length=255)
    type = models.ForeignKey('goals.AreaType',
                             verbose_name=_('Area type'),
                             related_name='areas')
    description = models.TextField(_('Area description'), blank=True)
    image = models.ImageField(_('Image'),
                              upload_to='goals/areas/images',
                              blank=True, null=True)
    image_small = ImageSpecField(source='image',
                                 processors=[ResizeToFit(100, 100)],
                                 format='PNG',
                                 options={'quality': 90})
    image_medium = ImageSpecField(source='image',
                                  processors=[ResizeToFit(250, 250)],
                                  format='PNG',
                                  options={'quality': 90})
    image_large = ImageSpecField(source='image',
                                 processors=[ResizeToFit(700)],
                                 options={'quality': 80})
    slug = models.SlugField(_('Slug'), blank=True)
    created = models.DateTimeField(_('Created'), auto_now_add=True)
    last_modified = models.DateTimeField(_('Last modified'),
                                         auto_now=True)
    extras = HStoreField(_('Extras'), blank=True, null=True, default={})

    class Meta:
        verbose_name = _('Area')
        verbose_name_plural = _('Areas')

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = self.get_slug()
        if self.type:
            self.extras['type_code'] = self.type.code
            self.extras['type_name'] = self.type.name
        super(Area, self).save(*args, **kwargs)

    def get_slug(self):
        if not self.slug:
            slug = slugify(self.name[:50])
            return slug
        return self.slug

    @cached_property
    def api_url(self):
        try:
            return reverse('area-detail', args=[self.pk])
        except:
            # API isn't installed
            # FIXME: Catch a specific exception
            return ''

    @cached_property
    def type_code(self):
        if self.type:
            return self.extras.get('type_code', '') or self.type.code
        return ""

    @cached_property
    def type_name(self):
        if self.type:
            return self.extras.get('type_name', '') or self.type.name
        return ""


Question: from django.utils import timezone
from django.template.defaultfilters import slugify
from django.db.models import Count, Prefetch
from rest_framework import viewsets
from .serializers import (AreaSerializer, AreaTypeSerializer,
                          PlanSerializer, ThemeSerializer, GoalSerializer,
                          SectorTypeSerializer, SectorSerializer,
                          TargetSerializer, IndicatorSerializer,
                          ComponentSerializer, ProgressSerializer)
from ..models import (AreaType, Area, Plan, Theme, SectorType, Sector, Goal,
                      Target, Indicator, Component, Progress)
from ..filters import (AreaFilter, PlanFilter, GoalFilter, ThemeFilter,
                       SectorFilter, TargetFilter, IndicatorFilter,
                       ComponentFilter, ProgressFilter)


class ModelViewSet(viewsets.ModelViewSet):

    def finalize_response(self, request, response, *args, **kwargs):
        response = super(ModelViewSet, self).finalize_response(
            request, response, *args, **kwargs)
        filename = '%s-%s.csv' % (slugify(self.get_view_name()), timezone.now().isoformat())
        if response.accepted_renderer.format == 'csv':
            response['content-disposition'] = 'attachment; filename=%s' % filename
        return response


class AreaTypeViewSet(ModelViewSet):
    queryset = AreaType.objects.all()
    serializer_class = AreaTypeSerializer


class AreaViewSet(ModelViewSet):
    queryset = Area.objects.all()
    serializer_class = AreaSerializer
    filter_class = AreaFilter
    ordering_fields = ('id', 'code', 'name', 'type')


class PlanViewSet(ModelViewSet):
    queryset = Plan.objects.all()

Answer:     serializer_class = PlanSerializer