class ApplicationSerializer(serializers.ModelSerializer): url = make_hyperlinked_identity_field(**application_related_context(with_queryset=False)) datasets = make_hyperlinked_related_field(allow_empty=True, many=True, **dataset_related_context()) service = serializers.SlugRelatedField(queryset=Service.objects.all(), slug_field='cname') class Meta: model = Application fields = ['url', 'id', 'name', 'service', 'description', 'datasets'] def create(self, validated_data): app = Application.objects.create(name=validated_data['name'], service=validated_data['service'], description=validated_data['description']) for dataset in validated_data['datasets']: app.applicationdataset_set.add(ApplicationDataset.objects.create(app=app, dataset=dataset)) return app def update(self, instance, validated_data): instance.name = validated_data.get('name', instance.name) instance.service = validated_data.get('service', instance.service) # import pdb;pdb.set_trace() instance.description = validated_data.get('description', instance.description) members = [] old_datasets = set(member.dataset for member in instance.applicationdataset_set.all()) datasets = set(validated_data['datasets']) for dataset in datasets - old_datasets: members.append(ApplicationDataset.objects.create(app=instance, dataset=dataset)) for dataset in old_datasets - datasets: Mapper.objects.filter(app=instance, dataset=dataset).delete() for it in ApplicationDataset.objects.filter(app=instance, dataset=dataset): it.delete() instance.applicationdataset_set.set(members) instance.save() return instance
class DataSetViewSet(CustModelViewSet): """ 資料集: dataset_name (資料集名稱)、 datapoint_strategies (資料集的取點方法)、 singlepoints (單點資料點清單)、 mappers (顯示點&資料點Mapping)、 datastore (資料集存取點)。 """ queryset = Dataset.objects.all() serializer_class = DataSetSerializer lookup_field = dataset_related_context()['lookup_field'] filter_fields = ('dataset_name', 'id')
class DataSetSerializer(serializers.HyperlinkedModelSerializer): SINGLE_STRATEGIES = set([PointID, ClosestPair]) GRID_STRATEGIES = set([ApproachPoint, Polygon]) url = make_hyperlinked_identity_field(**dataset_related_context(with_queryset=False)) is_api_available = serializers.ChoiceField(choices=(0, 1), ) category = serializers.ChoiceField(choices=(SINGLE, GRID), ) datapoint_strategies = serializers.SlugRelatedField(queryset=DatapointStrategy.objects.all(), slug_field='strategy_name', many=True) class Meta: depth = 1 model = Dataset fields = ('url', 'id', 'dataset_name', 'category', 'description', 'is_api_available', 'datapoint_strategies') def to_representation(self, instance): data = super(type(self), self).to_representation(instance) data['singlepoints'] = _reverse('singlepoint-list', request=self.context['request']) + '?dataset=%d' % instance.id data['mappers'] = _reverse('mapper-list', request=self.context['request']) + '?dataset=%d' % instance.id try: data['datastore'] = _reverse(instance.dataset_name.lower() + '-list', request=self.context['request']) except Exception: data['datastore'] = None return data def create(self, validated_data): datapoint_strategies = self.__get_datapoint_strategies(validated_data) dataset = Dataset.objects.create(dataset_name=validated_data['dataset_name'], category=validated_data['category'], is_api_available=validated_data['is_api_available'], description=validated_data['description']) for datapoint_strategy in datapoint_strategies: dataset.datasetdatapointstrategy_set.add( DatasetDatapointStrategy.objects.create(dataset=dataset, datapoint_strategy=datapoint_strategy) ) return dataset def update(self, instance, validated_data): datapoint_strategies = set(validated_data['datapoint_strategies']) self.__check_datapoint_strategies(datapoint_strategies, instance.category) instance.dataset_name = validated_data.get('dataset_name', instance.dataset_name) instance.category = validated_data.get('category', instance.category) instance.is_api_available = validated_data.get('is_api_available', instance.is_api_available) instance.description = validated_data.get('description', instance.description) instance.save() members = [] old_datapoint_strategies = set(member.datapoint_strategy for member in instance.datasetdatapointstrategy_set.all()) for datapoint_strategy in datapoint_strategies - old_datapoint_strategies: member = DatasetDatapointStrategy.objects.create(datapoint_strategy=datapoint_strategy, dataset=instance) members.append(member) for datapoint_strategy in old_datapoint_strategies - datapoint_strategies: for member in DatasetDatapointStrategy.objects.filter(datapoint_strategy=datapoint_strategy, dataset=instance): member.delete() instance.datasetdatapointstrategy_set.set(members) return instance def __get_datapoint_strategies(self, validated_data): category = validated_data['category'] datapoint_strategies = set(validated_data['datapoint_strategies']) if not datapoint_strategies: datapoint_strategies = self.GRID_STRATEGIES if category == GRID else self.SINGLE_STRATEGIES self.__check_datapoint_strategies(datapoint_strategies, category) return datapoint_strategies def __check_datapoint_strategies(self, datapoint_strategies, category): if category == GRID: if not datapoint_strategies <= self.GRID_STRATEGIES: vas = ','.join(o.strategy_name for o in datapoint_strategies - self.GRID_STRATEGIES) raise ValidationError({'datapoint_strategies': "Invalid '%s' strategies for dataset type '%s'" % (vas, category)}) else: if not datapoint_strategies <= self.SINGLE_STRATEGIES: vas = ','.join(o.strategy_name for o in datapoint_strategies - self.SINGLE_STRATEGIES) raise ValidationError({'datapoint_strategies': "Invalid '%s' strategies for dataset type '%s'" % (vas, category)}) return datapoint_strategies
class MapperSerializer(DynamicFieldsMixin, serializers.HyperlinkedModelSerializer): id = serializers.IntegerField(read_only=True) view = serializers.PrimaryKeyRelatedField(label='view', help_text=u'* 顯示點 ID(必填)。', queryset=View.objects.all()) # view = make_hyperlinked_related_field(label='view', help_text=u'* 顯示點URL(必填)。', queryset=View.objects.all(), view_name='view-detail') dataset = make_hyperlinked_related_field(label='dataset', help_text=u'* 資料集URL(必填)。', **dataset_related_context()) singlepoint = serializers.CharField(required=False, read_only=True, allow_null=True) point_id = serializers.CharField(label='point_id', help_text=u'單點資料點 ID(選擇), 用來和 $view.name 做綁定。', required=False, allow_blank=True, write_only=True) lat = serializers.FloatField(label='lat', help_text=u'緯度(選擇),預設取 $view.lat。', required=False, allow_null=True) lng = serializers.FloatField(label='lng', help_text=u'經度(選擇),預設取 $view.lng。', required=False, allow_null=True) latlng = serializers.CharField(label='latlng', help_text=u'多邊形經緯度(選擇),預設取 $view.latlng,如:"23,120; 23,120.5; 22.6,120; 23,120;"。', required=False, allow_null=True, allow_blank=True) datapoint_strategy = serializers.SlugRelatedField(label='datapoint_strategy', help_text=u"綁定方法(選擇),單點資料預設取 'PointID',格點資料依參數而定。", allow_null=True, slug_field='strategy_name', queryset=DatapointStrategy.objects.all(), required=False,) class Meta: model = Mapper fields = ['url', 'id', 'view', 'dataset', 'datapoint_strategy', 'point_id', 'singlepoint', 'lat', 'lng', 'latlng', ] def to_representation(self, instance): data = super(MapperSerializer, self).to_representation(instance) data['view'] = ViewSerializer(instance.view, context=self.context).data data['singlepoint'] = SinglePointSerializer(instance.singlepoint, context=self.context).data if instance.singlepoint_id else None return data def validate(self, attrs): view = attrs['view'] dataset = attrs['dataset'] point_id = attrs.pop('point_id', None) attrs['app'] = app = view.app lat = attrs.get('lat') or view.lat lng = attrs.get('lng') or view.lng latlng = attrs.get('latlng') or view.latlng if dataset not in app.datasets.all(): msg = u"The dataset '%s' has not associated to the app '%s'." % (dataset.dataset_name, app.name) raise ValidationError({'dataset': [msg]}) datapoint_strategy = attrs['datapoint_strategy'] datapoint_strategies = set(o for o in dataset.datapoint_strategies.all()) if datapoint_strategy is None and dataset.category == SINGLE: datapoint_strategy = PointID if datapoint_strategy is None and dataset.category == GRID: datapoint_strategy = Polygon if latlng and latlng.strip() else ApproachPoint if datapoint_strategy not in datapoint_strategies: msg = "Invalid value, it should be an item of [%s]" % ','.join(o.strategy_name for o in datapoint_strategies) raise ValidationError({'datapoint_strategy': [msg]}) attrs['datapoint_strategy'] = datapoint_strategy strategy_name = datapoint_strategy.strategy_name if dataset.category == SINGLE: if strategy_name == PointID.strategy_name: attrs['singlepoint'] = singlepoint = self.associate_by_point_id(dataset, point_id or view.label) elif strategy_name == ClosestPair.strategy_name: attrs['singlepoint'] = singlepoint = self.associate_by_closest_pair(dataset, lat, lng) attrs['lat'], attrs['lng'] = lat, lng else: raise RuntimeError("Invalid datapoint_strategy '%s'." % strategy_name) viewset = self.context['view'] qs = Mapper.objects.filter(view=view, dataset=dataset) if viewset.action == 'create' and qs.filter(singlepoint=singlepoint).exists(): if strategy_name == PointID.strategy_name: msg = "The single point_id($view.label) '%s' had been associated with dataset '%s'." % ( singlepoint.point_id, dataset.dataset_name) raise ValidationError({'point_id': [msg]}) else: msg = "The SinglePoint '%s' close to location(%s, %s) in dataset '%s' had been associated with view '%s'." % ( singlepoint.id, lng, lat, dataset.dataset_name, view.id) raise ValidationError({'lat': [msg], 'lng': [msg]}) elif dataset.category == GRID: if strategy_name == ApproachPoint.strategy_name: self.associate_by_closest_point(dataset, lat, lng) attrs['lat'], attrs['lng'] = lat, lng elif strategy_name == Polygon.strategy_name: self.associate_by_polygeon(dataset, latlng) attrs['latlng'] = latlng else: raise RuntimeError("Invalid datapoint_strategy '%s'." % strategy_name) else: msg = u"can not handle datapoint_strategy_name '%s' dataset_category '%s'" % (strategy_name, dataset.category) err_logger.waring(msg) raise ValidationError({'detail': [msg]}) return attrs def associate_by_point_id(self, dataset, point_id): if point_id is None: raise ValidationError({'point_id': ['This may not be empty.']}) try: singlepoint = SinglePoint.objects.get(point_id=point_id, dataset=dataset) except ObjectDoesNotExist: raise ValidationError({'point_id': ["The point_id '%s' is not defined in the dataset '%s'." % (point_id, dataset.dataset_name)]}) return singlepoint def associate_by_closest_pair(self, dataset, lat, lng): try: ViewSerializer.validate_lat(lat, allow_null=False) except ValidationError as e: raise ValidationError({'lat': e.detail}) try: ViewSerializer.validate_lng(lng, allow_null=False) except ValidationError as e: raise ValidationError({'lng': e.detail}) request = self.context['request'] query_params = ["lat=%s&lng=%s" % (lat, lng)] urlencode = request.GET.urlencode() if urlencode: query_params.append(urlencode) url = _reverse(viewname='closestpoint-list', kwargs={'dataset_name': dataset.dataset_name}, request=self.context['request'], ) + '?' + '&'.join(query_params) url = url.replace('127.0.0.1', 'localhost') res = http_forward_get(url, self.context['request']) if res.status_code != 200: raise ValidationError(res.json()) single_point_url = res.json()['url'] return instance_from_url(single_point_url) def associate_by_closest_point(self, dataset, lat, lng): try: ViewSerializer.validate_lat(lat, allow_null=False) except ValidationError as e: raise ValidationError({'lat': e.detail}) try: ViewSerializer.validate_lng(lng, allow_null=False) except ValidationError as e: raise ValidationError({'lng': e.detail}) creator = LatLngURLCreator(dataset.dataset_name, lat, lng) if creator.has_url(): url = creator.get_url() try: json = http_intercept_get(url).json() except InterceptedException as e: raise ValidationError({'lat': [e.detail], 'lng': [e.detail]}) if not json.get('data') or str(json['data'][0]["dts"][0]["taus"][0]["val"]) == str(-9999): latlng = "%s,%s; %s,%s; %s,%s; %s,%s; %s,%s;" % ( lat + 0.25, lng - 0.25, lat + 0.25, lng + 0.25, lat - 0.25, lng + 0.25, lat - 0.25, lng - 0.25, lat + 0.25, lng - 0.25) msg = u"無效的點Point(%s, %s)超出了資料集 '%s' 的定義範圍. 建議您使用Polygon([%s])取值。" % (lat, lng, dataset.dataset_name, latlng) raise ValidationError({'lat': [msg], 'lng': [msg]}) return self def associate_by_polygeon(self, dataset, latlng): try: ViewSerializer.validate_latlng(latlng, allow_null=False) except ValidationError as e: raise ValidationError({'latlng': e.detail}) creator = PolygenURLCreator(dataset.dataset_name, latlng) if creator.has_url(): url = creator.get_url() try: json = http_intercept_get(url).json() except InterceptedException as e: raise ValidationError({'latlng': [e.detail]}) if not json.get('data') or str(json['data'][0]["dts"][0]["taus"][0]["val"]) == str(-9999): msg = u"無效的區域 Polygon([%s]) 超出了資料集'%s'的定義範圍,請重新劃定區域。" % (latlng, dataset.dataset_name) raise ValidationError({'latlng': [msg]}) else: err_logger.warning(u"無法對資料集 '%s' 的資料取點 Polygon('%s')。" % (dataset.dataset_name, latlng)) return self
class SinglePointSerializer(serializers.HyperlinkedModelSerializer): dataset = make_hyperlinked_related_field(**dataset_related_context()) class Meta: model = SinglePoint fields = ['url', 'id', 'point_id', 'dataset', 'name', 'lat', 'lng', ]